Skip to content

Conversation

LuisUrrutia
Copy link
Contributor

@LuisUrrutia LuisUrrutia commented Sep 12, 2025

Summary

  • Polygon zkEVM support for zkevm_estimate* RPC methods. They recomends the usage of those methods for gas and fee estimation, but no all RPC server supports them.
  • Polygon zkEVM testnet is not supported anymore, so we switched to use Polygon zkEVM Cardona Testnet.

Testing Process

Checklist

  • Add a reference to related issues in the PR description.
  • Add unit tests if applicable.

Summary by CodeRabbit

  • New Features

    • Native Polygon zkEVM support for gas/fee estimation with automatic network detection.
  • Improvements

    • Unified fee model (l1_fee) and improved finalization for EIP-1559 and legacy transactions.
    • New fetcher framework with zkEVM-aware and default strategies; graceful RPC fallback when methods are unavailable.
    • Provider errors expose explicit "method not available" cases.
  • Bug Fixes

    • Polygon zkEVM testnet chain ID, explorer and RPC endpoints updated; network tags standardized.
  • Tests

    • Expanded zkEVM and transaction-data tests.
  • Chores / Breaking

    • L2 fee module removed (Optimism L2-specific fee service discontinued).

@LuisUrrutia LuisUrrutia requested review from a team as code owners September 12, 2025 11:26
@coderabbitai
Copy link

coderabbitai bot commented Sep 12, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds Polygon zkEVM tagging/detection, a zkEVM price handler and fetcher, refactors EVM price pipeline to use l1_fee (replacing extra_fee) with cap/finalize steps, expands ProviderError for -32601, removes the L2 fee module, and updates polygon network config and related tests.

Changes

Cohort / File(s) Summary
Network config & tags
config/networks/polygon.json, src/constants/network_tags.rs, src/models/network/evm/network.rs
Add polygon-zkevm-based tag to networks, update polygon-zkevm-testnet chain_id/rpc/explorer, add POLYGON_ZKEVM_TAG constant and EvmNetwork::is_polygon_zkevm() + tests.
Price params & calculator
src/domain/transaction/evm/price_calculator.rs, src/domain/transaction/evm/evm_transaction.rs, src/domain/transaction/evm/replacement.rs
Replace extra_feel1_fee on PriceParams; restructure pricing pipeline into fetch → apply_gas_price_cap_and_constraints → finalize_price_params; remove unused helpers; add MAX_BASE_FEE_MULTIPLIER; update tests.
Handlers & dispatch
src/services/gas/handlers/mod.rs, src/services/gas/handlers/polygon_zkevm.rs, src/services/gas/handlers/optimism.rs, src/services/gas/handlers/test_mock.rs, src/services/gas/price_params_handler.rs
Add PolygonZKEvmPriceHandler (generic), switch handlers and dispatch to use EvmTransactionData, wire polygon zkEVM detection into PriceParamsHandler, update tests/mocks to l1_fee and new data type.
Provider error mapping
src/services/provider/mod.rs
Add ProviderError::MethodNotAvailable(String) and map JSON‑RPC -32601 to this variant.
Gas price fetcher framework
src/services/gas/fetchers/*, src/services/gas/mod.rs, src/services/gas/cache.rs, src/services/gas/evm_gas_price.rs
Introduce fetchers module, GasPriceFetcher enum and GasPriceFetcherFactory; add default and polygon_zkevm fetchers; replace direct provider.get_gas_price() calls with factory usage; add zkevm network detection test.
Polygon zkEVM fetcher & default fetcher
src/services/gas/fetchers/polygon_zkevm.rs, src/services/gas/fetchers/default.rs
Add PolygonZkEvmGasPriceFetcher with zkevm_estimateGasPrice probe, parsing and fallback-to-standard; add DefaultGasPriceFetcher delegating to provider; include tests for parsing, fallbacks, and errors.
Removed L2 fee module
src/services/gas/l2_fee.rs
Remove L2 fee service and public APIs (L2FeeData/L2FeeService and factory), eliminating Optimism L2-fee-specific machinery.
Minor wiring & tests
src/services/gas/handlers/*, src/services/gas/cache.rs, src/models/transaction/repository.rs
Update tests/mocks to use EvmTransactionData, l1_fee, and new handler/fetcher types; adjust imports and function signatures accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant PriceCalc as PriceCalculator
  participant Selector as PriceParamsHandler
  participant OptimismH as OptimismHandler
  participant ZkEvmH as PolygonZkEVMHandler

  Client->>PriceCalc: get_transaction_price_params(tx)
  PriceCalc->>PriceCalc: fetch_price_params_based_on_tx_type
  PriceCalc->>PriceCalc: apply_gas_price_cap_and_constraints
  PriceCalc->>Selector: handle_price_params(tx, params)

  alt network.is_polygon_zkevm()
    Selector->>ZkEvmH: handle_price_params(tx, params)
    ZkEvmH-->>Selector: params (total_cost updated via zkevm_estimateFee)
  else network.is_optimism()
    Selector->>OptimismH: handle_price_params(tx, params)
    OptimismH-->>Selector: params'
  else
    Selector-->>PriceCalc: params
  end

  PriceCalc->>PriceCalc: finalize_price_params
  PriceCalc-->>Client: final PriceParams (includes l1_fee / total_cost)
Loading
sequenceDiagram
  autonumber
  participant FetchFactory as GasPriceFetcherFactory
  participant DefaultF as DefaultGasPriceFetcher
  participant ZkFetch as PolygonZkEvmGasPriceFetcher
  participant Provider

  FetchFactory->>FetchFactory: create_for_network(network)
  alt network.is_polygon_zkevm()
    FetchFactory->>ZkFetch: fetch_gas_price(provider, network)
    ZkFetch->>Provider: zkevm_estimateGasPrice (probe)
    alt returns price
      ZkFetch-->>FetchFactory: price
    else method not available / invalid
      ZkFetch->>Provider: get_gas_price() (fallback)
      ZkFetch-->>FetchFactory: price or error
    end
  else
    FetchFactory->>DefaultF: fetch_gas_price(provider, network)
    DefaultF->>Provider: get_gas_price()
    DefaultF-->>FetchFactory: price or error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • dylankilkenny
  • zeljkoX

Poem

I thump my paw — a tag appears,
zk hops, fetchers chase the gears.
I nibble l1 fees, cap and bind,
If methods hide, fallbacks find.
Carrots saved — the rabbit grins, code well-lined. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description includes a clear Summary that describes zkevm_estimate* support and the testnet swap, but the "## Testing Process" section is empty and the Checklist items (reference to related issues and unit tests) are left unchecked; given the raw changes include many test and handler edits, the absence of concrete testing details and missing issue references makes the description incomplete. Please populate the "## Testing Process" with concrete steps and results (commands run, CI job/pass status, how to reproduce tests), add or link related issue(s) in the PR description, and list or point to the unit tests added/updated (file or test names), then update the Checklist to reflect those additions so reviewers can verify coverage and validation.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat(polygon-zkevm): Support for custom estimation methods" is concise and accurately summarizes the primary change in the diff—adding Polygon zkEVM support for zkevm_estimate* estimation paths (handlers, fetchers, and network/tag updates)—so it is clear and relevant for teammates scanning history.
Docstring Coverage ✅ Passed Docstring coverage is 89.29% which is sufficient. The required threshold is 80.00%.

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.

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: 4

Caution

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

⚠️ Outside diff range comments (3)
src/services/gas/handlers/optimism.rs (3)

72-83: Ceil the compressed size to avoid undercharging L1 data gas.

Integer division floors; rounding down underestimates cost. Use ceil-div by 16.

-        ((zero_bytes * U256::from(4)) + (non_zero_bytes * U256::from(16))) / U256::from(16)
+        let calldata_gas = (zero_bytes * U256::from(4)) + (non_zero_bytes * U256::from(16));
+        // ceil((zero*4 + non_zero*16)/16)
+        (calldata_gas + U256::from(15)) / U256::from(16)

107-121: Use GasPriceOracle decimals in the L1/blob weighting; current code likely overestimates.

decimals is fetched but never applied. Per OP/Bedrock oracles, scalars are fixed-point and must be divided by decimals. Add safe division for both components.

-        let weighted_gas_price = U256::from(16)
-            .saturating_mul(U256::from(fee_data.base_fee_scalar))
-            .saturating_mul(U256::from(fee_data.l1_base_fee))
-            + U256::from(fee_data.blob_base_fee_scalar)
-                .saturating_mul(U256::from(fee_data.blob_base_fee));
+        let denom = std::cmp::max(fee_data.decimals, U256::from(1));
+        let weighted_l1 = U256::from(16)
+            .saturating_mul(fee_data.base_fee_scalar)
+            .saturating_mul(fee_data.l1_base_fee)
+            .saturating_div(denom);
+        let weighted_blob = fee_data
+            .blob_base_fee_scalar
+            .saturating_mul(fee_data.blob_base_fee)
+            .saturating_div(denom);
+        let weighted_gas_price = weighted_l1.saturating_add(weighted_blob);

123-144: Let the calculator finalize total_cost after caps; don’t set it here.

The price calculator re-applies caps after the handler. Precomputing total_cost here can make it stale vs. capped values.

-        // Recalculate total cost with the extra fee
-        let gas_limit = tx.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT);
-        let value = tx.value;
-        let is_eip1559 = original_params.max_fee_per_gas.is_some();
-
-        original_params.total_cost =
-            original_params.calculate_total_cost(is_eip1559, gas_limit, value);
+        // Defer total_cost recomputation to the finalizer (after caps are re-applied)
🧹 Nitpick comments (8)
src/services/provider/mod.rs (1)

40-42: Map JSON-RPC -32601 to a distinct error type

Good addition. Consider adding a focused unit test asserting that -32601 maps to MethodNotAvailable and is not treated as retriable.

Would you like me to add a small test in this module’s test suite to cover this case?

Also applies to: 150-159

src/services/provider/evm/mod.rs (1)

176-188: Optional: avoid mixing legacy and EIP-1559 fields

When maxFeePerGas/maxPriorityFeePerGas are set, omit gasPrice in tx_params to reduce ambiguity.

src/services/gas/handlers/polygon_zkevm.rs (2)

14-19: Docs contradict behavior on fallbacks

Comment says “no fallbacks needed” but code returns original params when methods are unavailable. Update wording.

Apply this diff:

-/// - Directly leverages the accurate pricing data from the gas price service (no fallbacks needed)
+/// - Prefers zkEVM endpoints; gracefully falls back to original params if methods are unavailable

29-59: Optional: parallelize RPC calls to reduce latency

Run zkevm_estimate_gas_price and zkevm_estimate_fee concurrently (try_join) and handle MethodNotAvailable in either result.

src/services/gas/handlers/optimism.rs (1)

164-179: LGTM on EvmTransactionData migration; add a stronger assertion.

Since oracle mocks return zeros, assert extra_fee == Some(0) to harden the test.

-        // Extra fee should be added
-        assert!(handled_params.extra_fee.is_some());
+        // Extra fee should be present and zero with mocked oracle
+        assert_eq!(handled_params.extra_fee, Some(U256::ZERO));

Also applies to: 208-223

src/domain/transaction/evm/price_calculator.rs (1)

544-587: Be explicit when neither legacy nor EIP-1559 fields are set.

Today the else-branch treats “all None” as legacy and caps 0. Consider early-returning an error in that case to avoid silently producing zero pricing.

-        } else {
+        } else if let Some(gp) = price_params.gas_price {
             // Handle legacy transaction
-            price_params.gas_price = Some(Self::cap_gas_price(
-                price_params.gas_price.unwrap_or_default(),
+            price_params.gas_price = Some(Self::cap_gas_price(
+                gp,
                 gas_price_cap,
             ));
             // For legacy transactions, EIP1559 fields should be None
             price_params.max_fee_per_gas = None;
             price_params.max_priority_fee_per_gas = None;
+        } else {
+            return Err(TransactionError::InvalidType(
+                "Missing gas price parameters".to_string(),
+            ));
         }
src/services/gas/price_params_handler.rs (2)

29-40: Dispatch order/ownership: OK; consider documenting precedence.

If a network ever carries multiple tags, document which handler wins (Polygon ZK EVM currently takes precedence).


97-110: Solid unit test for Polygon ZK EVM selection.

Optional: also assert handler actually calls into zkEVM estimation (mock provider) in an integration-style test.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da2d90e and af83871.

📒 Files selected for processing (11)
  • config/networks/polygon.json (1 hunks)
  • src/constants/network_tags.rs (1 hunks)
  • src/domain/transaction/evm/price_calculator.rs (3 hunks)
  • src/models/network/evm/network.rs (3 hunks)
  • src/services/gas/handlers/mod.rs (1 hunks)
  • src/services/gas/handlers/optimism.rs (8 hunks)
  • src/services/gas/handlers/polygon_zkevm.rs (1 hunks)
  • src/services/gas/handlers/test_mock.rs (2 hunks)
  • src/services/gas/price_params_handler.rs (7 hunks)
  • src/services/provider/evm/mod.rs (1 hunks)
  • src/services/provider/mod.rs (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-28T11:19:51.397Z
Learnt from: LuisUrrutia
PR: OpenZeppelin/openzeppelin-relayer#427
File: src/constants/network_tags.rs:5-6
Timestamp: 2025-08-28T11:19:51.397Z
Learning: In the OpenZeppelin relayer codebase, when deprecating constants that are still used internally (like OPTIMISM_TAG in LACKS_MEMPOOL_TAGS), use doc comments instead of #[deprecated] attribute to avoid clippy failures during the transition period.

Applied to files:

  • src/constants/network_tags.rs
📚 Learning: 2025-08-19T14:31:09.686Z
Learnt from: LuisUrrutia
PR: OpenZeppelin/openzeppelin-relayer#427
File: src/models/network/evm/network.rs:1-1
Timestamp: 2025-08-19T14:31:09.686Z
Learning: In the OpenZeppelin relayer codebase, test helper functions may use network names (like "optimism") as string literals for creating mock networks, which are different from network tag constants (like OPTIMISM_BASED_TAG). These should not be flagged as inconsistencies when reviewing tag-related changes.

Applied to files:

  • src/constants/network_tags.rs
🧬 Code graph analysis (4)
src/services/gas/handlers/polygon_zkevm.rs (3)
src/services/gas/handlers/optimism.rs (1)
  • handle_price_params (123-144)
src/services/gas/price_params_handler.rs (1)
  • handle_price_params (46-63)
src/services/provider/mod.rs (9)
  • from (47-49)
  • from (53-55)
  • from (59-61)
  • from (102-104)
  • from (108-110)
  • from (114-122)
  • from (127-129)
  • from (137-162)
  • from (167-169)
src/services/provider/evm/mod.rs (1)
src/services/provider/mod.rs (9)
  • from (47-49)
  • from (53-55)
  • from (59-61)
  • from (102-104)
  • from (108-110)
  • from (114-122)
  • from (127-129)
  • from (137-162)
  • from (167-169)
src/domain/transaction/evm/price_calculator.rs (3)
src/domain/transaction/evm/evm_transaction.rs (1)
  • relayer (123-125)
src/models/transaction/repository.rs (3)
  • is_eip1559 (421-421)
  • is_eip1559 (430-432)
  • from (1046-1053)
src/models/relayer/repository.rs (2)
  • from (85-97)
  • from (101-128)
src/services/gas/price_params_handler.rs (4)
src/domain/transaction/evm/price_calculator.rs (1)
  • new (156-161)
src/services/gas/handlers/optimism.rs (1)
  • new (31-36)
src/services/gas/handlers/polygon_zkevm.rs (1)
  • new (25-27)
src/services/provider/evm/mod.rs (3)
  • new (214-236)
  • new (614-624)
  • setup_test_env (635-638)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: clippy
  • GitHub Check: msrv
  • GitHub Check: test
  • GitHub Check: Redirect rules - openzeppelin-relayer
  • GitHub Check: Header rules - openzeppelin-relayer
  • GitHub Check: Pages changed - openzeppelin-relayer
  • GitHub Check: Analyze (rust)
  • GitHub Check: semgrep/ci
🔇 Additional comments (12)
src/services/gas/handlers/mod.rs (1)

4-4: Expose Polygon zkEVM handler — looks good

Public module and re-export are correctly wired for dispatch.

Also applies to: 9-9

src/constants/network_tags.rs (1)

5-5: Add POLYGON_ZKEVM_TAG — looks good

Tag name aligns with config and classification; no change to LACKS_MEMPOOL_TAGS is appropriate.

src/models/network/evm/network.rs (1)

3-5: Polygon zkEVM classification and tests — looks good

Import, is_polygon_zkevm(), and the test validate the tag-driven detection correctly.

Also applies to: 131-134, 257-266

src/services/gas/handlers/test_mock.rs (1)

3-3: Mock updated to EvmTransactionData — looks good

Signature and behavior remain consistent for tests.

Also applies to: 16-16

config/networks/polygon.json (2)

1-26: Note on summary discrepancy (no action required)

The first “polygon” mainnet entry doesn’t include "tags": ["polygon-zkevm"] in this file, contrary to the AI summary. Treat the code as source of truth.


61-65: Cardona chain ID and RPC verified — no changes required.
Chain ID 2442 is correct for Polygon zkEVM Cardona (testnet), and https://rpc.cardona.zkevm-rpc.com is the recommended/public testnet RPC endpoint.

src/services/provider/evm/mod.rs (1)

158-166: Reject object-parsing refactor — zkevm_estimateGasPrice and zkevm_estimateFee return hex strings

Verified: both RPCs return hex‑encoded wei as plain strings in the JSON‑RPC "result" field; the suggested diff to accept object shapes is unnecessary and incorrect.
Location: src/services/provider/evm/mod.rs (lines 158–166 and 194–201) — keep current string parsing.

Likely an incorrect or invalid review comment.

src/domain/transaction/evm/price_calculator.rs (2)

183-198: Pipeline refactor is clean and readable.

Good separation (fetch → cap → handler → finalize). Resetting transient fields before finalization is sensible.


268-271: Finalization hook usage looks correct.

Delegating to finalize_price_params keeps bump logic focused and consistent.

src/services/gas/price_params_handler.rs (3)

18-19: New PolygonZKEvm handler variant looks good.

Enum expansion and type wiring are coherent with existing Optimism path.


47-55: Signature switched to EvmTransactionData: LGTM.

Async delegation per variant is consistent.


140-155: EvmTransactionData construction in tests is correct.

Fields align with the new model.

Comment on lines 593 to 615
/// Applies price params handler and finalizes price parameters.
async fn finalize_price_params(
&self,
relayer: &RelayerRepoModel,
tx_data: &EvmTransactionData,
mut price_params: PriceParams,
) -> Result<PriceParams, TransactionError> {
let is_eip1559 = tx_data.is_eip1559();

// Apply price params handler if available
let mut recompute_total_cost = true;
if let Some(handler) = &self.price_params_handler {
price_params = handler.handle_price_params(tx_data, price_params).await?;

// Re-apply cap after handler in case it changed fee fields
self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;

recompute_total_cost = price_params.total_cost == U256::ZERO;
}

// Only recompute total cost if it was not set by the handler
if recompute_total_cost {
price_params.total_cost = price_params.calculate_total_cost(
is_eip1559,
tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
U256::from(tx_data.value),
);
}

Ok(price_params)
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix EIP-1559 detection during finalization; use PriceParams, not tx_data.

Speed-based txs can yield EIP-1559 params even when tx_data.is_eip1559() is false. Using tx_data here can miscompute total_cost.

-        let is_eip1559 = tx_data.is_eip1559();
+        let is_eip1559 = price_params.max_fee_per_gas.is_some()
+            && price_params.max_priority_fee_per_gas.is_some();
@@
-            price_params.total_cost = price_params.calculate_total_cost(
-                is_eip1559,
-                tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
-                U256::from(tx_data.value),
-            );
+            price_params.total_cost = price_params.calculate_total_cost(
+                is_eip1559,
+                tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
+                tx_data.value,
+            );
📝 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
/// Applies price params handler and finalizes price parameters.
async fn finalize_price_params(
&self,
relayer: &RelayerRepoModel,
tx_data: &EvmTransactionData,
mut price_params: PriceParams,
) -> Result<PriceParams, TransactionError> {
let is_eip1559 = tx_data.is_eip1559();
// Apply price params handler if available
let mut recompute_total_cost = true;
if let Some(handler) = &self.price_params_handler {
price_params = handler.handle_price_params(tx_data, price_params).await?;
// Re-apply cap after handler in case it changed fee fields
self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
recompute_total_cost = price_params.total_cost == U256::ZERO;
}
// Only recompute total cost if it was not set by the handler
if recompute_total_cost {
price_params.total_cost = price_params.calculate_total_cost(
is_eip1559,
tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
U256::from(tx_data.value),
);
}
Ok(price_params)
}
/// Applies price params handler and finalizes price parameters.
async fn finalize_price_params(
&self,
relayer: &RelayerRepoModel,
tx_data: &EvmTransactionData,
mut price_params: PriceParams,
) -> Result<PriceParams, TransactionError> {
let is_eip1559 = price_params.max_fee_per_gas.is_some()
&& price_params.max_priority_fee_per_gas.is_some();
// Apply price params handler if available
let mut recompute_total_cost = true;
if let Some(handler) = &self.price_params_handler {
price_params = handler.handle_price_params(tx_data, price_params).await?;
// Re-apply cap after handler in case it changed fee fields
self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
recompute_total_cost = price_params.total_cost == U256::ZERO;
}
// Only recompute total cost if it was not set by the handler
if recompute_total_cost {
price_params.total_cost = price_params.calculate_total_cost(
is_eip1559,
tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
tx_data.value,
);
}
Ok(price_params)
}

Copy link
Collaborator

@zeljkoX zeljkoX left a comment

Choose a reason for hiding this comment

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

Thanks for your work!

Left few comments!

Seems like tests are failing due to missing import.

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: 0

♻️ Duplicate comments (2)
src/domain/transaction/evm/price_calculator.rs (1)

588-614: Fix EIP-1559 detection and avoid double-conversion of value when recomputing total_cost.

Use the finalized PriceParams to decide EIP‑1559, not tx_data (speed-based or handler overrides can flip the mode). Also use tx_data.value directly (it’s already U256).

Apply:

-        let is_eip1559 = tx_data.is_eip1559();
+        let is_eip1559 = price_params.max_fee_per_gas.is_some()
+            && price_params.max_priority_fee_per_gas.is_some();

@@
-            price_params.total_cost = price_params.calculate_total_cost(
-                is_eip1559,
-                tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
-                U256::from(tx_data.value),
-            );
+            price_params.total_cost = price_params.calculate_total_cost(
+                is_eip1559,
+                tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
+                tx_data.value,
+            );

Please add a unit test where tx_data is speed-based (no 1559 fields) but handler returns 1559 params; assert total_cost uses 1559 path.

config/networks/polygon.json (1)

67-82: Rename polygon-zkevm-testnet → polygon-zkevm-cardona and fix explorer/RPC (chain_id 2442).

config/networks/polygon.json (line 69) currently sets "network": "polygon-zkevm-testnet" but "chain_id" is 2442 (Cardona). Rename the entry to "polygon-zkevm-cardona", update explorer_urls to Cardona endpoints, add a couple public RPC fallbacks for resiliency, or mark the old name as deprecated if you must keep it temporarily.

       "from": "polygon-zkevm",
       "type": "evm",
-      "network": "polygon-zkevm-testnet",
+      "network": "polygon-zkevm-cardona",
       "chain_id": 2442,
       "explorer_urls": [
-        "https://api-testnet-zkevm.polygonscan.com/api",
-        "https://testnet-zkevm.polygonscan.com"
+        "https://api-cardona-zkevm.polygonscan.com/api",
+        "https://cardona-zkevm.polygonscan.com"
       ],
       "is_testnet": true,
       "rpc_urls": [
-        "https://rpc.cardona.zkevm-rpc.com"
+        "https://rpc.cardona.zkevm-rpc.com",
+        "https://polygon-zkevm-cardona.drpc.org",
+        "https://polygon-zkevm-cardona.public.blastapi.io"
       ],
       "tags": [
-        "polygon-zkevm-based"
+        "polygon-zkevm-based"
       ]

If retaining the old name temporarily, add the deprecated tag:

-      "tags": [
-        "polygon-zkevm-based"
-      ]
+      "tags": [
+        "polygon-zkevm-based",
+        "deprecated"
+      ]
🧹 Nitpick comments (1)
src/domain/transaction/evm/price_calculator.rs (1)

539-582: Harden constraints: error on empty pricing; cap priority by cap then by maxFee.

  • If all fee fields are None, we silently set gas_price to 0. Better to error.
  • When EIP‑1559 is provided, first cap priority by gas_price_cap, then ensure it does not exceed capped maxFee.

Proposed changes:

     fn apply_gas_price_cap_and_constraints(
         &self,
-        price_params: &mut PriceParams,
+        price_params: &mut PriceParams,
         relayer: &RelayerRepoModel,
     ) -> Result<(), TransactionError> {
@@
-        if let (Some(max_fee), Some(max_priority)) = (
+        if let (Some(max_fee), Some(max_priority)) = (
             price_params.max_fee_per_gas,
             price_params.max_priority_fee_per_gas,
         ) {
             // Cap the maxFeePerGas
             let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
             price_params.max_fee_per_gas = Some(capped_max_fee);
 
-            // Ensure maxPriorityFeePerGas < maxFeePerGas to avoid client errors
-            price_params.max_priority_fee_per_gas =
-                Some(Self::cap_gas_price(max_priority, capped_max_fee));
+            // Cap priority by cap, then ensure <= maxFee
+            let capped_priority_by_cap = Self::cap_gas_price(max_priority, gas_price_cap);
+            price_params.max_priority_fee_per_gas =
+                Some(std::cmp::min(capped_priority_by_cap, capped_max_fee));
 
             // For EIP1559 transactions, gas_price should be None
             price_params.gas_price = None;
-        } else {
+        } else if let Some(gp) = price_params.gas_price {
             // Handle legacy transaction
-            price_params.gas_price = Some(Self::cap_gas_price(
-                price_params.gas_price.unwrap_or_default(),
-                gas_price_cap,
-            ));
+            price_params.gas_price = Some(Self::cap_gas_price(gp, gas_price_cap));
 
             // For legacy transactions, EIP1559 fields should be None
             price_params.max_fee_per_gas = None;
             price_params.max_priority_fee_per_gas = None;
+        } else {
+            return Err(TransactionError::NotSupported(
+                "Missing gas price parameters".to_string(),
+            ));
         }
 
         Ok(())
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af83871 and f346b42.

📒 Files selected for processing (4)
  • config/networks/polygon.json (1 hunks)
  • src/constants/network_tags.rs (1 hunks)
  • src/domain/transaction/evm/price_calculator.rs (3 hunks)
  • src/services/gas/handlers/polygon_zkevm.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/constants/network_tags.rs
  • src/services/gas/handlers/polygon_zkevm.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/domain/transaction/evm/price_calculator.rs (2)
src/models/transaction/repository.rs (3)
  • is_eip1559 (421-421)
  • is_eip1559 (430-432)
  • from (1046-1053)
src/models/relayer/repository.rs (2)
  • from (85-97)
  • from (101-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: clippy
  • GitHub Check: msrv
  • GitHub Check: test
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: Analyze (rust)
  • GitHub Check: Redirect rules - openzeppelin-relayer
  • GitHub Check: Header rules - openzeppelin-relayer
  • GitHub Check: Pages changed - openzeppelin-relayer
🔇 Additional comments (3)
config/networks/polygon.json (1)

61-64: Tagging zkEVM mainnet looks good.

Adding "polygon-zkevm-based" to the zkEVM mainnet entry is correct and will help route handler logic.

src/domain/transaction/evm/price_calculator.rs (2)

183-193: Pipeline step ordering LGTM.

Capping before finalization, then re-applying caps post-handler, is sound for safety.


263-266: Finalize after bump LGTM.

Deferring handler application and total-cost finalization until after bumping is correct.

@codecov
Copy link

codecov bot commented Sep 17, 2025

Codecov Report

❌ Patch coverage is 98.24807% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.9%. Comparing base (2a8cdde) to head (06bb2f2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/services/gas/fetchers/mod.rs 95.5% 7 Missing ⚠️
src/services/gas/fetchers/polygon_zkevm.rs 97.5% 7 Missing ⚠️
src/services/gas/handlers/polygon_zkevm.rs 99.0% 5 Missing ⚠️
src/services/gas/cache.rs 89.2% 3 Missing ⚠️
src/services/gas/price_params_handler.rs 88.4% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            main    #461     +/-   ##
=======================================
  Coverage   92.8%   92.9%             
=======================================
  Files        233     238      +5     
  Lines      83197   84503   +1306     
=======================================
+ Hits       77233   78512   +1279     
- Misses      5964    5991     +27     
Flag Coverage Δ
integration 0.5% <0.0%> (-0.1%) ⬇️
properties <0.1% <0.0%> (-0.1%) ⬇️
unittests 92.9% <98.2%> (+<0.1%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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: 0

♻️ Duplicate comments (1)
src/domain/transaction/evm/price_calculator.rs (1)

544-582: Fix EIP-1559 detection during finalization; use PriceParams, not tx_data.

Speed-based txs can yield EIP-1559 params even when tx_data.is_eip1559() is false. Using tx_data here can miscompute total_cost.

🧹 Nitpick comments (15)
src/domain/transaction/evm/price_calculator.rs (1)

589-614: Consider removing the second cap application after handler

The gas price caps are already applied in apply_gas_price_cap_and_constraints before the handler is invoked (line 188). Re-applying them after the handler (line 602) may be redundant unless the handler intentionally exceeds caps. If handlers are expected to respect caps, this second application could be removed for clarity. If handlers may exceed caps for specific networks (e.g., special pricing rules), document this behavior.

Consider simplifying to:

 async fn finalize_price_params(
     &self,
     relayer: &RelayerRepoModel,
     tx_data: &EvmTransactionData,
     mut price_params: PriceParams,
 ) -> Result<PriceParams, TransactionError> {
-    let is_eip1559 = tx_data.is_eip1559();
+    // Determine pricing type from actual params, not tx_data
+    let is_eip1559 = price_params.max_fee_per_gas.is_some() 
+        && price_params.max_priority_fee_per_gas.is_some();
 
     // Apply price params handler if available
     if let Some(handler) = &self.price_params_handler {
         price_params = handler.handle_price_params(tx_data, price_params).await?;
 
-        // Re-apply cap after handler in case it changed fee fields
-        self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
+        // Note: If handler needs to exceed caps for specific networks, 
+        // document this behavior and keep the re-application
     }
 
     if price_params.total_cost == U256::ZERO {
         price_params.total_cost = price_params.calculate_total_cost(
             is_eip1559,
             tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
             U256::from(tx_data.value),
         );
     }
 
     Ok(price_params)
 }
src/services/gas/evm_gas_price.rs (1)

303-305: Consider deduplicating the gas price fetching logic.

The gas price fetching logic is duplicated between the cached and non-cached paths. Consider extracting this into a helper method to reduce code duplication.

+    async fn fetch_gas_price_with_factory(&self) -> Result<u128, TransactionError> {
+        GasPriceFetcherFactory::fetch_gas_price(&self.provider, &self.network)
+            .await
+            .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))
+    }

     async fn get_legacy_prices_from_json_rpc(&self) -> Result<SpeedPrices, TransactionError> {
         let base = if let Some(cache) = &self.cache {
             // ... cache logic ...
-                GasPriceFetcherFactory::fetch_gas_price(&self.provider, &self.network)
-                    .await
-                    .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?
+                self.fetch_gas_price_with_factory().await?
             }
         } else {
-            GasPriceFetcherFactory::fetch_gas_price(&self.provider, &self.network)
-                .await
-                .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?
+            self.fetch_gas_price_with_factory().await?
         };
src/services/gas/fetchers/default.rs (2)

64-64: Fix test name typo.

The test is named test_default_estimator_failure but should be test_default_fetcher_failure to maintain consistency with the implementation naming.

-    async fn test_default_estimator_failure() {
+    async fn test_default_fetcher_failure() {

92-116: Fix inconsistent test names.

Several test methods use "estimator" instead of "fetcher" in their names, which is inconsistent with the implementation.

-    async fn test_default_estimator_network_error() {
+    async fn test_default_fetcher_network_error() {

-    async fn test_default_estimator_ethereum() {
+    async fn test_default_fetcher_ethereum() {

-    async fn test_default_estimator_polygon() {
+    async fn test_default_fetcher_polygon() {
src/services/gas/cache.rs (1)

477-502: Test placement could be improved.

The test_polygon_zkevm_network_detection test is validating EvmNetwork::is_polygon_zkevm() functionality, which belongs to the network model. Consider moving this test to the appropriate network model test file for better organization.

This test should be moved to src/models/network/evm/network.rs test module or a dedicated test file for the EvmNetwork model, as it's testing network detection logic rather than cache functionality.

src/services/gas/fetchers/polygon_zkevm.rs (5)

41-46: Consider documenting RPC provider requirements.

Since not all RPC providers support the zkevm_estimateGasPrice method, consider adding documentation (either in code comments or external docs) about which providers are known to support this method and how users can verify support.


76-79: Consider using debug level for successful price fetches.

The successful gas price fetch currently logs at info level. Since this happens frequently during normal operation, consider using debug level to reduce log noise, similar to the factory's logging pattern.

-                log::info!(
+                log::debug!(
                     "zkEVM gas price estimated for chain_id {}: {} wei",

101-105: Align fallback logging with zkEVM success logging.

For consistency with the previous suggestion, consider using debug level for the fallback success case as well.

-                log::info!(
+                log::debug!(
                     "Using standard gas price fallback for zkEVM chain_id {}: {} wei",

164-193: Test naming inconsistency.

The test is named test_zkevm_estimator_method_not_available but should be test_zkevm_fetcher_method_not_available to maintain consistency with the implementation naming.

-    async fn test_zkevm_estimator_method_not_available() {
+    async fn test_zkevm_fetcher_method_not_available() {

195-271: Fix inconsistent test names throughout.

Multiple test methods use "estimator" instead of "fetcher" in their names.

-    async fn test_zkevm_estimator_invalid_response() {
+    async fn test_zkevm_fetcher_invalid_response() {

-    async fn test_zkevm_estimator_invalid_hex_response() {
+    async fn test_zkevm_fetcher_invalid_hex_response() {

-    async fn test_zkevm_estimator_other_error() {
+    async fn test_zkevm_fetcher_other_error() {

-    async fn test_zkevm_estimator_both_methods_fail() {
+    async fn test_zkevm_fetcher_both_methods_fail() {

-    async fn test_zkevm_estimator_hex_with_0x_prefix() {
+    async fn test_zkevm_fetcher_hex_with_0x_prefix() {

-    async fn test_zkevm_estimator_hex_without_0x_prefix() {
+    async fn test_zkevm_fetcher_hex_without_0x_prefix() {
src/services/gas/fetchers/mod.rs (2)

16-25: Missing documentation for the PolygonZkEvm variant

The PolygonZkEvm variant lacks a documentation comment similar to the Default variant on line 23.

 pub enum GasPriceFetcher {
     /// Default EVM gas price fetcher using standard `eth_gasPrice`
     Default(default::DefaultGasPriceFetcher),
+    /// Polygon zkEVM gas price fetcher with custom estimation methods
     PolygonZkEvm(polygon_zkevm::PolygonZkEvmGasPriceFetcher),
 }

72-80: Consider consolidating error handling patterns

The error handling for Ok(None) creates a new error with generic text. Consider using a more specific error type or constant to improve maintainability and consistency across the codebase.

             Ok(None) => {
                 log::warn!(
                     "Fetcher returned None for supported network chain_id {}",
                     network.chain_id
                 );
-                Err(ProviderError::Other(
-                    "Fetcher failed to provide gas price for supported network".to_string(),
-                ))
+                Err(ProviderError::GasPriceUnavailable(format!(
+                    "No gas price available for chain_id {}",
+                    network.chain_id
+                )))
             }

Note: This assumes ProviderError::GasPriceUnavailable is or can be added to the error enum.

src/services/gas/handlers/polygon_zkevm.rs (2)

18-31: Consider validating the to address format

The function defaults to "0x" for missing to address on line 21, which represents a contract creation. However, there's no validation that existing to addresses are properly formatted hex strings with 0x prefix.

Consider adding address validation or documenting that callers are responsible for providing valid addresses:

 fn build_zkevm_transaction_params(tx: &EvmTransactionData) -> serde_json::Value {
+    // Note: Assumes `from` and `to` (if present) are valid hex addresses with 0x prefix
     serde_json::json!({
         "from": tx.from,
         "to": tx.to.as_ref().unwrap_or(&"0x".to_string()),

105-176: Mock tests duplicate production code logic

The MockZkEvmHandler implementation (lines 147-175) duplicates the exact same logic as the production handle_price_params method. This creates maintenance burden and doesn't truly test the production code.

Consider using a mock provider instead of reimplementing the handler logic:

-    struct MockZkEvmHandler {
-        fee_estimate_result: MockFeeEstimateResult,
-    }
-
-    impl MockZkEvmHandler {
-        // ... duplicate implementation ...
-    }
+    use crate::services::provider::evm::MockEvmProviderTrait;
+    use futures::FutureExt;
+
+    async fn test_with_mock_provider(
+        fee_response: serde_json::Value,
+    ) -> Result<PriceParams, TransactionError> {
+        let mut mock_provider = MockEvmProviderTrait::new();
+        mock_provider
+            .expect_raw_request_dyn()
+            .returning(move |_, _| {
+                let response = fee_response.clone();
+                async move { Ok(response) }.boxed()
+            });
+        
+        let handler = PolygonZKEvmPriceHandler::new(mock_provider);
+        // ... rest of test
+    }
src/services/gas/price_params_handler.rs (1)

91-95: Consider extracting test environment setup to a shared test utility

The setup_test_env() function sets environment variables that are used across multiple test files. Consider moving this to a shared test utilities module.

This would reduce duplication and ensure consistent test setup across the codebase.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88a4b6a and 0ec9fbc.

📒 Files selected for processing (16)
  • config/networks/polygon.json (2 hunks)
  • src/domain/transaction/evm/evm_transaction.rs (5 hunks)
  • src/domain/transaction/evm/price_calculator.rs (19 hunks)
  • src/domain/transaction/evm/replacement.rs (3 hunks)
  • src/models/transaction/repository.rs (1 hunks)
  • src/services/gas/cache.rs (3 hunks)
  • src/services/gas/evm_gas_price.rs (4 hunks)
  • src/services/gas/fetchers/default.rs (1 hunks)
  • src/services/gas/fetchers/mod.rs (1 hunks)
  • src/services/gas/fetchers/polygon_zkevm.rs (1 hunks)
  • src/services/gas/handlers/optimism.rs (9 hunks)
  • src/services/gas/handlers/polygon_zkevm.rs (1 hunks)
  • src/services/gas/handlers/test_mock.rs (2 hunks)
  • src/services/gas/l2_fee.rs (0 hunks)
  • src/services/gas/mod.rs (1 hunks)
  • src/services/gas/price_params_handler.rs (7 hunks)
💤 Files with no reviewable changes (1)
  • src/services/gas/l2_fee.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/networks/polygon.json
🧰 Additional context used
🧬 Code graph analysis (8)
src/services/gas/fetchers/polygon_zkevm.rs (2)
src/services/gas/fetchers/default.rs (1)
  • fetch_gas_price (17-26)
src/services/gas/fetchers/mod.rs (2)
  • fetch_gas_price (29-40)
  • fetch_gas_price (57-90)
src/services/gas/fetchers/mod.rs (2)
src/services/gas/fetchers/default.rs (1)
  • fetch_gas_price (17-26)
src/services/gas/fetchers/polygon_zkevm.rs (1)
  • fetch_gas_price (18-27)
src/domain/transaction/evm/price_calculator.rs (2)
src/models/transaction/repository.rs (3)
  • from (1046-1053)
  • is_eip1559 (421-421)
  • is_eip1559 (430-432)
src/models/relayer/response.rs (6)
  • from (49-61)
  • from (152-168)
  • from (172-196)
  • from (446-457)
  • from (461-477)
  • from (481-488)
src/services/gas/fetchers/default.rs (2)
src/services/gas/fetchers/mod.rs (2)
  • fetch_gas_price (29-40)
  • fetch_gas_price (57-90)
src/services/gas/fetchers/polygon_zkevm.rs (1)
  • fetch_gas_price (18-27)
src/services/gas/evm_gas_price.rs (3)
src/services/gas/fetchers/default.rs (1)
  • fetch_gas_price (17-26)
src/services/gas/fetchers/mod.rs (2)
  • fetch_gas_price (29-40)
  • fetch_gas_price (57-90)
src/services/gas/fetchers/polygon_zkevm.rs (1)
  • fetch_gas_price (18-27)
src/services/gas/cache.rs (4)
src/services/gas/fetchers/default.rs (1)
  • fetch_gas_price (17-26)
src/services/gas/fetchers/mod.rs (2)
  • fetch_gas_price (29-40)
  • fetch_gas_price (57-90)
src/services/gas/fetchers/polygon_zkevm.rs (1)
  • fetch_gas_price (18-27)
src/models/network/evm/network.rs (1)
  • is_polygon_zkevm (131-133)
src/services/gas/handlers/polygon_zkevm.rs (3)
src/services/provider/mod.rs (9)
  • from (47-49)
  • from (53-55)
  • from (59-61)
  • from (102-104)
  • from (108-110)
  • from (114-122)
  • from (127-129)
  • from (137-162)
  • from (167-169)
src/services/gas/handlers/optimism.rs (2)
  • new (31-36)
  • handle_price_params (123-144)
src/services/gas/price_params_handler.rs (1)
  • handle_price_params (46-63)
src/services/gas/price_params_handler.rs (4)
src/domain/transaction/evm/price_calculator.rs (1)
  • new (156-161)
src/services/gas/handlers/optimism.rs (1)
  • new (31-36)
src/services/gas/handlers/polygon_zkevm.rs (1)
  • new (45-47)
src/services/provider/evm/mod.rs (2)
  • new (159-181)
  • new (559-569)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test
  • GitHub Check: clippy
  • GitHub Check: msrv
  • GitHub Check: Analyze (rust)
🔇 Additional comments (29)
src/domain/transaction/evm/price_calculator.rs (1)

79-79: Clear semantic improvement: l1_fee field name is more descriptive

The rename from extra_fee to l1_fee better communicates the purpose of this field as Layer 1 fees for L2 networks like Optimism and Polygon zkEVM.

src/models/transaction/repository.rs (1)

1229-1229: LGTM!

Test correctly updated to use the renamed l1_fee field.

src/services/gas/mod.rs (1)

1-4: Documentation update accurately reflects module's purpose

Good update to the module documentation - "fetching and calculation" is more accurate than "estimation and calculation" since the module retrieves existing gas prices from the network rather than estimating future prices.

src/domain/transaction/evm/evm_transaction.rs (5)

916-917: Test correctly uses renamed l1_fee field

The test has been properly updated to use l1_fee instead of extra_fee in the PriceParams struct.


1016-1017: Test correctly uses renamed l1_fee field

The test has been properly updated to use l1_fee instead of extra_fee in the PriceParams struct.


1173-1173: Test correctly uses renamed l1_fee field with L1 fee value

The test has been properly updated to use l1_fee with a non-None value (U256::ZERO), correctly reflecting L1 fee scenarios.


1353-1353: Test correctly uses renamed l1_fee field with L1 fee value

The test has been properly updated to use l1_fee with a non-None value (U256::ZERO) for replacement transaction pricing.


1838-1838: Test correctly uses renamed l1_fee field

The test has been properly updated to use l1_fee instead of extra_fee in the PriceParams struct for gas estimation tests.

src/domain/transaction/evm/replacement.rs (3)

132-132: Test correctly uses renamed l1_fee field

The field rename from extra_fee to l1_fee has been properly applied in the validate_explicit_price_bump function.


401-401: Mock correctly uses renamed l1_fee field

The MockPriceCalculator test implementation has been properly updated to use l1_fee instead of extra_fee.


420-420: Mock correctly uses renamed l1_fee field

The MockPriceCalculator's calculate_bumped_gas_price method has been properly updated to use l1_fee instead of extra_fee.

src/services/gas/evm_gas_price.rs (2)

298-300: LGTM! Clean integration of the gas price fetcher factory.

The transition from direct provider.get_gas_price() calls to using GasPriceFetcherFactory::fetch_gas_price() is well-implemented. This abstraction enables network-specific gas pricing strategies (e.g., zkEVM's custom methods) while maintaining backward compatibility through the default fetcher.


488-492: Test comment update is accurate.

The test comment correctly reflects that the default fetcher is now being used under the hood, maintaining the same expected behavior.

src/services/gas/fetchers/default.rs (1)

16-26: LGTM! Simple and effective default implementation.

The DefaultGasPriceFetcher correctly delegates to the provider's standard get_gas_price() method, providing a clean fallback for non-zkEVM networks.

src/services/gas/cache.rs (1)

263-266: LGTM! Good integration with the fetcher factory in cache refresh.

The cache refresh logic correctly uses GasPriceFetcherFactory for fetching gas prices, ensuring consistent behavior across the codebase.

src/services/gas/handlers/test_mock.rs (1)

14-22: LGTM! Consistent with the broader refactoring.

The changes correctly update the mock handler to use EvmTransactionData and the new l1_fee field, aligning with the PR's overall refactoring from extra_fee to l1_fee.

src/services/gas/fetchers/polygon_zkevm.rs (1)

17-27: LGTM! Well-structured fetcher with proper fallback logic.

The implementation follows a clear strategy: attempt zkEVM-specific method first, then fallback to standard gas price fetching. This ensures compatibility across different RPC providers.

src/services/gas/fetchers/mod.rs (2)

145-161: LGTM! Well-structured test for default fetcher

The test correctly validates the default gas price fetcher behavior and uses appropriate mocking.


163-185: LGTM! Comprehensive zkEVM fetcher test

The test properly validates the zkEVM-specific RPC method call and response parsing.

src/services/gas/handlers/optimism.rs (4)

1-11: LGTM! Import changes align with data model refactoring

The transition from EvmTransactionRequest to EvmTransactionData is correctly implemented in the imports.


72-83: LGTM! Function signature update is consistent

The calculate_compressed_tx_size function correctly accepts the new EvmTransactionData type.


123-144: LGTM! Field rename from extra_fee to l1_fee is properly implemented

The handler correctly:

  1. Updates the l1_fee field instead of extra_fee (line 133)
  2. Recalculates total cost with the L1 fee included
  3. Maintains backward compatibility with the calculation logic

164-179: Tests correctly updated for the new data model

The test data structure properly includes all new required fields (nonce, chain_id, hash, signature, raw) for EvmTransactionData.

src/services/gas/handlers/polygon_zkevm.rs (2)

74-102: LGTM! Clean error handling for zkEVM methods

The handler properly:

  1. Returns original params when methods are unavailable (graceful fallback)
  2. Maps provider errors to TransactionError
  3. Sets total_cost directly from the zkEVM estimate instead of using l1_fee field

This approach correctly handles the zkEVM case where the estimate already includes all costs.


513-600: LGTM! Comprehensive test coverage for transaction parameter building

The test thoroughly validates:

  • Hex formatting for all numeric fields
  • Proper handling of missing optional fields
  • Automatic 0x prefix addition for data fields
src/services/gas/price_params_handler.rs (4)

17-22: LGTM! Clean enum design for handler variants

The enum structure properly encapsulates different network-specific handlers with appropriate conditional compilation for the test mock.


28-40: LGTM! Correct network detection and handler instantiation

The factory method properly:

  1. Checks for Polygon zkEVM first (line 29)
  2. Falls back to Optimism check (line 33)
  3. Returns None for unsupported networks

46-63: LGTM! Clean delegation pattern

The match arms properly delegate to each handler variant, maintaining a consistent interface.


97-109: LGTM! Test correctly validates Polygon zkEVM handler creation

The test properly verifies that networks tagged with POLYGON_ZKEVM_TAG get the correct handler variant.

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: 0

♻️ Duplicate comments (1)
src/services/gas/fetchers/polygon_zkevm.rs (1)

11-11: Fix unresolved log crate (or align with repo’s logging).
GHAS flagged “use of unresolved module log.” Ensure the dependency is present or switch to tracing if that’s the project standard.

Add (if using log):

# Cargo.toml
[dependencies]
log = "0.4"

Or switch imports/usages to tracing consistently.

Run to verify what the repo uses:

#!/bin/bash
rg -nP -C2 '^\s*use\s+(log|tracing)::' && rg -nP -C2 '\b(log|tracing)::(debug|info|warn|error)!\('
🧹 Nitpick comments (6)
src/services/gas/fetchers/polygon_zkevm.rs (6)

14-15: Derive Default for zero-field fetcher.
Minor ergonomics; enables construction via Default.

-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Default)]
 pub struct PolygonZkEvmGasPriceFetcher;

31-38: Avoid string literal drift: extract method name const.
Reduces typos and eases reuse/tests.

+const ZKEVM_ESTIMATE_GAS_PRICE: &str = "zkevm_estimateGasPrice";
@@
-            .raw_request_dyn("zkevm_estimateGasPrice", serde_json::Value::Array(vec![]))
+            .raw_request_dyn(ZKEVM_ESTIMATE_GAS_PRICE, serde_json::Value::Array(vec![]))

59-89: Make parsing more permissive; handle whitespace and 0X prefix.
Some RPCs are loose with casing/whitespace.

-        let Some(gas_price_hex) = response.as_str() else {
+        let Some(raw) = response.as_str() else {
             warn!(
                 "Invalid zkEVM gas price response format for chain_id {}",
                 chain_id
             );
             return Ok(None);
         };
-
-        match u128::from_str_radix(gas_price_hex.trim_start_matches("0x"), 16) {
+        let s = raw.trim();
+        let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
+        match u128::from_str_radix(s, 16) {
             Ok(gas_price) => {
-                info!(
+                debug!(
                     "zkEVM gas price estimated for chain_id {}: {} wei",
                     chain_id, gas_price
                 );
                 Ok(Some(gas_price))

Also consider making this a free fn (no &self). Optional:

-    fn parse_zkevm_response(
-        &self,
+    fn parse_zkevm_response(

73-79: Use debug instead of info to reduce log noise.
These are frequent/expected paths.

-                info!(
+                debug!(
                     "zkEVM gas price estimated for chain_id {}: {} wei",
                     chain_id, gas_price
                 );
-                info!(
+                debug!(
                     "Using standard gas price fallback for zkEVM chain_id {}: {} wei",
                     network.chain_id, standard_price
                 );

Also applies to: 97-104


106-111: Include the underlying error in the log message.
Improves triage.

-                error!(
-                    "Both zkEVM and standard gas price methods failed for chain_id {}",
-                    network.chain_id
-                );
+                error!(
+                    "Both zkEVM and standard gas price methods failed for chain_id {}: {}",
+                    network.chain_id, e
+                );

339-369: Add a couple more parse edge-case tests.
Uppercase 0X, zero value, and surrounding whitespace.

#[test]
fn test_parse_zkevm_response_uppercase_prefix_and_spaces() {
    let fetcher = PolygonZkEvmGasPriceFetcher;
    let response = serde_json::Value::String("  0X3b9aca00 ".to_string());
    let result = fetcher.parse_zkevm_response(response, 1101);
    assert_eq!(result.unwrap(), Some(1_000_000_000u128));
}

#[test]
fn test_parse_zkevm_response_zero() {
    let fetcher = PolygonZkEvmGasPriceFetcher;
    let response = serde_json::Value::String("0x0".to_string());
    let result = fetcher.parse_zkevm_response(response, 1101);
    assert_eq!(result.unwrap(), Some(0u128));
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ec9fbc and 0957b75.

📒 Files selected for processing (2)
  • src/services/gas/fetchers/mod.rs (1 hunks)
  • src/services/gas/fetchers/polygon_zkevm.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/gas/fetchers/mod.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/gas/fetchers/polygon_zkevm.rs (2)
src/services/gas/fetchers/mod.rs (2)
  • fetch_gas_price (30-41)
  • fetch_gas_price (58-89)
src/services/gas/fetchers/default.rs (1)
  • fetch_gas_price (17-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Redirect rules - openzeppelin-relayer
  • GitHub Check: Header rules - openzeppelin-relayer
  • GitHub Check: Pages changed - openzeppelin-relayer
  • GitHub Check: test
  • GitHub Check: clippy
  • GitHub Check: msrv
  • GitHub Check: boostsecurity - boostsecurityio/semgrep-pro
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
src/services/gas/fetchers/polygon_zkevm.rs (4)

1-6: Good module docs and clear intent.
Concise, explains fallback behavior.


19-28: Fetcher orchestration LGTM.
Early-return on zkEVM and clean fallback.


116-137: Test fixture LGTM.
Accurate Polygon zkEVM mainnet params for unit tests.


139-338: Great test coverage of success/fallback/error paths.
Covers method-not-available, invalid formats, and hex variants.

Copy link
Collaborator

@zeljkoX zeljkoX left a comment

Choose a reason for hiding this comment

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

Thanks for improving implementation.

I see that we now have option to extend gas price logic and logic to extend price calculations.
Recent change removes overhead from previous implementation.

LGTM

Remaining item to discuss is breaking change with field renaming.

@dylankilkenny If you find time it would be great to get second pair of eyes on this PR. Thanks

Copy link
Member

@dylankilkenny dylankilkenny left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Collaborator

@zeljkoX zeljkoX left a comment

Choose a reason for hiding this comment

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

LGTM

@LuisUrrutia LuisUrrutia force-pushed the polygon-zkevm-support-2 branch from 422795f to e4e9905 Compare October 16, 2025 10:53
@LuisUrrutia LuisUrrutia requested a review from a team as a code owner October 16, 2025 10:53
* main:
  feat(stellar): Add Turnkey signer support (#495)
  feat: Relayer health check job (#506)
  refactor: Handle RPC error codes on retries (#514)
@LuisUrrutia LuisUrrutia merged commit 1180d92 into main Oct 17, 2025
24 of 25 checks passed
@LuisUrrutia LuisUrrutia deleted the polygon-zkevm-support-2 branch October 17, 2025 11:26
@github-actions github-actions bot locked and limited conversation to collaborators Oct 17, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants