Skip to content

Conversation

@zeljkoX
Copy link
Collaborator

@zeljkoX zeljkoX commented Dec 5, 2025

Summary

This PR will extend logic to allow bumping gas price for NOOP transactions. This is required in order to make sure relayer stays healthy and able to process new txs.

Use case: tx expires but system is not able to replace properly with NOOP and submit due to price was not bumped by 10% due to gas price cap policy.

Testing Process

Checklist

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

Note

If you are using Relayer in your stack, consider adding your team or organization to our list of Relayer Users in the Wild!

Summary by CodeRabbit

  • New Features

    • Enhanced gas price bumping logic with improved handling for specific transaction types.
    • Default transaction speed now automatically assigned during transaction creation.
  • Tests

    • Updated test mocks and expectations to reflect new gas price calculation behavior and transaction speed initialization.

✏️ Tip: You can customize this high-level summary in your review settings.

@zeljkoX zeljkoX requested a review from a team as a code owner December 5, 2025 14:12
@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

This PR introduces noop-aware gas price bumping by adding a force_bump parameter to the gas price calculation flow. It includes early EVM data retrieval, parameter threading through call sites in transaction processing and resubmission paths, updates to related mocks and tests, and default speed assignment for NOOP transactions.

Changes

Cohort / File(s) Summary
Price Calculator Trait & Implementation
src/domain/transaction/evm/price_calculator.rs
Added force_bump: bool parameter to PriceCalculatorTrait::calculate_bumped_gas_price; updated PriceCalculator implementation to accept and propagate the parameter; adjusted bump calculation to bypass relayer gas price cap when force_bump is true; updated tests and call sites.
EVM Transaction Processing
src/domain/transaction/evm/evm_transaction.rs
Moved EVM data retrieval earlier; threaded noop-aware force_bump (derived from is_noop()) through calculate_bumped_gas_price calls in both initial and resubmission paths; updated test mocks to expect the new parameter; adjusted error handling and price param propagation.
Test Replacement Mock
src/domain/transaction/evm/replacement.rs
Updated MockPriceCalculator::calculate_bumped_gas_price signature to include _force_bump: bool parameter; mock behavior unchanged.
NOOP Transaction Utilities
src/domain/transaction/evm/utils.rs
Added import for DEFAULT_TRANSACTION_SPEED; updated make_noop to assign default speed to transaction data; added test assertions for speed propagation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Price calculation bypass logic (src/domain/transaction/evm/price_calculator.rs): verify the force_bump-conditional cap bypass is correct and doesn't introduce unintended pricing behavior
  • Noop detection and force_bump derivation (src/domain/transaction/evm/evm_transaction.rs): ensure is_noop() check correctly identifies transactions requiring forced bumping and that the parameter flows through both primary and resubmission paths
  • Data fetch ordering: confirm early EVM data retrieval doesn't introduce race conditions or redundant I/O

Possibly related PRs

  • feat: replace l2 extra fees with price overriders #460: Both PRs modify the EVM price-calculation path — this PR adds a force_bump parameter to calculate_bumped_gas_price, while #460 refactors the PriceCalculator to use PriceParamsHandler.
  • chore: Improves EVM sent -> submitted flow #577: Both PRs modify the EVM resubmission flow in evm_transaction.rs — this PR adds noop-aware force_bump plumbing and signature changes, while #577 adjusts the sent→submitted status update path.
  • feat: gas price cache #422: Both PRs modify EVM price-calculation code (PriceCalculatorTrait and call sites) — this PR's force_bump signature change is directly related to #422's price calculator refactors.

Suggested labels

cla: allowlist

Suggested reviewers

  • LuisUrrutia
  • dylankilkenny

Poem

🐰 A bump to the gas, a force on its way,
Noop transactions getting their say,
Early data fetch, no wasteful delay,
Through every call site, the parameter will play! ⛽✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete: the 'Summary' section explains the feature, but 'Testing Process' is empty and the 'Checklist' items are unchecked with no references to related issues or confirmation of unit tests added. Complete the 'Testing Process' section describing how the changes were tested, check the checklist items, add related issue references, and document which unit tests were added.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly relates to the main change: adding gas price bumping logic for EVM noop transactions, which is the core functionality introduced across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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 evm_noop_price_bump

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

Caution

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

⚠️ Outside diff range comments (1)
src/domain/transaction/evm/price_calculator.rs (1)

55-67: force_bump currently does not bypass the gas price cap due to re‑capping in finalize_price_params

Right now calculate_bumped_gas_price only uses force_bump when choosing the cap passed into handle_eip1559_bump / handle_legacy_bump:

  • For force_bump = false, relayer_gas_price_cap is the relayer policy cap.
  • For force_bump = true, relayer_gas_price_cap = u128::MAX, so the helpers don’t cap and compute is_min_bumped against uncapped values.

However, after those helpers return, you always call:

self.finalize_price_params(relayer, tx_data, bumped_price_params).await

and finalize_price_params unconditionally calls apply_gas_price_cap_and_constraints, which re‑reads gas_price_cap from the relayer policy and caps the prices again. As a result:

  • Final PriceParams.gas_price / max_fee_per_gas are still bounded by the relayer’s cap, even when force_bump = true.
  • The new doc comment (“apply caps unless force_bump is true”) and the test test_calculate_bumped_gas_price_force_bump_bypasses_cap are not actually satisfied.
  • NOOP resubmissions invoked with force_bump = true will still be limited by the relayer gas price cap, so the PR’s goal (being able to bump NOOPs above the cap to make them replaceable) is not met.

I’d recommend threading force_bump through to finalize_price_params (and into the internal cap application) so that the cap is truly skipped for forced bumps, while still applied for normal pricing. Conceptually:

-    pub async fn calculate_bumped_gas_price(
+    pub async fn calculate_bumped_gas_price(
         &self,
         tx_data: &EvmTransactionData,
         relayer: &RelayerRepoModel,
-        force_bump: bool,
+        force_bump: bool,
     ) -> Result<PriceParams, TransactionError> {
         // ... existing logic ...
-        self.finalize_price_params(relayer, tx_data, bumped_price_params)
-            .await
+        self.finalize_price_params(relayer, tx_data, bumped_price_params, force_bump)
+            .await
     }
@@
-    async fn finalize_price_params(
+    async fn finalize_price_params(
         &self,
         relayer: &RelayerRepoModel,
         tx_data: &EvmTransactionData,
         mut price_params: PriceParams,
-    ) -> Result<PriceParams, TransactionError> {
+        force_bump: bool,
+    ) -> Result<PriceParams, TransactionError> {
         let is_eip1559 = tx_data.is_eip1559();
@@
-        if let Some(handler) = &self.price_params_handler {
+        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)?;
+            // Re-apply cap after handler in case it changed fee fields,
+            // unless we’re in a force_bump path (e.g., NOOP resubmission).
+            if !force_bump {
+                self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
+            }
         }
@@
-        if price_params.total_cost == U256::ZERO {
+        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),
             );
         }

and adjust the callers:

-        self.finalize_price_params(relayer, tx_data, price_final_params)
-            .await
+        self.finalize_price_params(relayer, tx_data, price_final_params, false)
+            .await

This way:

  • Normal pricing (get_transaction_price_params) continues to honour the relayer gas price cap.
  • Bump flows with force_bump = true can exceed the cap as intended (including your new test), while still respecting min‑bump logic and any extra validations you might add later.

Also consider updating apply_gas_price_cap_and_constraints’s doc comment (or adding a short note near calculate_bumped_gas_price) to clarify that caps are intentionally skipped when force_bump is used.

Also applies to: 136-151, 197-245, 559-595

🧹 Nitpick comments (4)
src/domain/transaction/evm/utils.rs (1)

23-27: NOOP speed normalization looks good; consider confirming overwrite semantics and extending tests

Setting evm_data.speed = Some(DEFAULT_TRANSACTION_SPEED) ensures is_noop reliably detects NOOPs (since it requires speed.is_some()) and gives pricing a deterministic speed. This does overwrite any existing speed (e.g., Speed::Fast) when converting to a NOOP, which is probably what you want but is worth double‑checking for all networks. The standard‑network test now asserts the default speed; you might optionally add similar assertions in the Arbitrum tests to lock in the behaviour there as well.

Also applies to: 58-64, 274-304

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

383-410: MockPriceCalculator updated correctly for new force_bump parameter

The mock implementation’s signature now matches PriceCalculatorTrait::calculate_bumped_gas_price, and ignoring _force_bump is fine for current tests. If you later add tests that depend on force_bump semantics, you can adapt the returning closure to assert on the flag.

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

13-22: NOOP‑aware force_bump wiring in resubmit_transaction looks correct; check backwards compatibility of is_noop

Using is_noop(&evm_data) to drive the force_bump argument into calculate_bumped_gas_price is a good fit with the new pricing API and keeps cap‑bypassing tightly scoped to genuine NOOP transactions. One subtle point: is_noop also requires speed.is_some(), so any pre‑existing NOOP records that were created before make_noop started setting speed will not benefit from force_bump and will still be capped. If those can exist in production, you may want a migration/backfill or a slightly more relaxed NOOP predicate for the resubmit path.

Also applies to: 766-786


1113-1130: Mocks updated for force_bump; consider asserting on the flag in a dedicated resubmit test

The test MockPriceCalculator now accepts the force_bump: bool argument, and existing tests simply ignore it via _ in the closure, which keeps them compiling. For extra confidence that NOOP resubmissions actually pass force_bump = true, you could add a focused test where the mock calculate_bumped_gas_price asserts on the boolean (e.g., panics if force_bump is false) when resubmitting a known NOOP transaction. This would catch any future regressions in the wiring.

Also applies to: 2728-2740

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd95c6d and 2fd6c85.

📒 Files selected for processing (4)
  • src/domain/transaction/evm/evm_transaction.rs (4 hunks)
  • src/domain/transaction/evm/price_calculator.rs (22 hunks)
  • src/domain/transaction/evm/replacement.rs (1 hunks)
  • src/domain/transaction/evm/utils.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust_standards.mdc)

**/*.rs: Follow official Rust style guidelines using rustfmt (edition = 2021, max_width = 100)
Follow Rust naming conventions: snake_case for functions/variables, PascalCase for types, SCREAMING_SNAKE_CASE for constants and statics
Order imports alphabetically and group by: std, external crates, local crates
Include relevant doc comments (///) on public functions, structs, and modules. Use comments for 'why', not 'what'. Avoid redundant doc comments
Document lifetime parameters when they're not obvious
Avoid unsafe code unless absolutely necessary; justify its use in comments
Write idiomatic Rust: Prefer Result over panic, use ? operator for error propagation
Avoid unwrap; handle errors explicitly with Result and custom Error types for all async operations
Prefer header imports over function-level imports of dependencies
Prefer borrowing over cloning when possible
Use &str instead of &String for function parameters
Use &[T] instead of &Vec for function parameters
Avoid unnecessary .clone() calls
Use Vec::with_capacity() when size is known in advance
Use explicit lifetimes only when necessary; infer where possible
Always use Tokio runtime for async code. Await futures eagerly; avoid blocking calls in async contexts
Streams: Use futures::StreamExt for processing streams efficiently
Always use serde for JSON serialization/deserialization with #[derive(Serialize, Deserialize)]
Use type aliases for complex types to improve readability
Implement common traits (Debug, Clone, PartialEq) where appropriate, using derive macros when possible
Implement Display for user-facing types
Prefer defining traits when implementing services to make mocking and testing easier
For tests, prefer existing utils for creating mocks instead of duplicating code
Use assert_eq! and assert! macros appropriately
Keep tests minimal, deterministic, and fast
Use tracing for structured logging, e.g., tracing::info! for request and error logs
When optimizing, prefer clarity first, then performance
M...

Files:

  • src/domain/transaction/evm/price_calculator.rs
  • src/domain/transaction/evm/utils.rs
  • src/domain/transaction/evm/evm_transaction.rs
  • src/domain/transaction/evm/replacement.rs
🧬 Code graph analysis (2)
src/domain/transaction/evm/price_calculator.rs (4)
src/domain/transaction/evm/replacement.rs (1)
  • calculate_bumped_gas_price (404-422)
src/services/gas/evm_gas_price.rs (3)
  • new (163-173)
  • default (33-40)
  • default (52-58)
src/models/relayer/response.rs (1)
  • default (97-112)
src/utils/mocks.rs (1)
  • create_mock_relayer (51-73)
src/domain/transaction/evm/evm_transaction.rs (1)
src/domain/transaction/evm/utils.rs (1)
  • is_noop (59-64)
⏰ 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: Redirect rules - openzeppelin-relayer
  • GitHub Check: Header rules - openzeppelin-relayer
  • GitHub Check: Pages changed - openzeppelin-relayer
  • GitHub Check: clippy
  • GitHub Check: test
  • GitHub Check: msrv
  • GitHub Check: Analyze (rust)
  • GitHub Check: semgrep/ci
🔇 Additional comments (1)
src/domain/transaction/evm/price_calculator.rs (1)

197-216: force_bump documentation and tests align with intended behaviour once cap skipping is fixed

The extended docs for calculate_bumped_gas_price (steps 4–5 and the new force_bump argument description) plus test_calculate_bumped_gas_price_force_bump_bypasses_cap clearly describe the desired semantics: forced bumps should ignore the relayer gas price cap while still satisfying min‑bump constraints. Once you adjust finalize_price_params / cap application as suggested, this test becomes an excellent regression guard to ensure NOOP resubmissions really can bypass the cap when required.

Also applies to: 2161-2249

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants