Skip to content

feat(price-pusher): migrate EVM pusher to EIP-1559 gas with legacy fallback - #3785

Open
aditya520 wants to merge 1 commit into
mainfrom
hydra/i-xbjjxgmg/head
Open

feat(price-pusher): migrate EVM pusher to EIP-1559 gas with legacy fallback#3785
aditya520 wants to merge 1 commit into
mainfrom
hydra/i-xbjjxgmg/head

Conversation

@aditya520

@aditya520 aditya520 commented Jun 1, 2026

Copy link
Copy Markdown
Member

Migrates the EVM price pusher from deprecated legacy gasPrice transactions to EIP-1559 (type-2) transactions, with a --legacy CLI flag for chains that don't support EIP-1559.

Changes

  • common.ts: Added GasParams discriminated union type (eip1559 | legacy) and updated PushAttempt to use it.
  • evm.ts: Refactored gas handling into helper methods (getGasParams, applyStaticOverride, escalateGas, capGas, gasToTxParams) that work with both EIP-1559 and legacy params. Default path uses estimateFeesPerGas().
  • command.ts: Added --legacy boolean CLI flag (default false), passed through to EvmPricePusher.
  • custom-gas-station.ts: Returns structured CustomGasResult with maxFeePerGas/maxPriorityFeePerGas in EIP-1559 mode, or gasPrice in legacy mode. Polygon gas station now returns both fee fields.

Testing

  • TypeScript compiles cleanly (pnpm turbo build passes)

Open in Devin Review

…llback

Default to EIP-1559 (type-2) transactions using maxFeePerGas and
maxPriorityFeePerGas from viem's estimateFeesPerGas(). Operators can
pass --legacy to use the old gasPrice model for chains without EIP-1559
support.

Changes:
- common.ts: Add GasParams discriminated union type and update PushAttempt
- evm.ts: Refactor gas handling into helper methods (getGasParams,
  applyStaticOverride, escalateGas, capGas, gasToTxParams) that work
  with both EIP-1559 and legacy params
- command.ts: Add --legacy boolean CLI flag, pass through to pusher
- custom-gas-station.ts: Return structured CustomGasResult with both
  maxFeePerGas/maxPriorityFeePerGas in EIP-1559 mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a6c24f992

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +226 to +227
if (a.type === "eip1559" && b.type === "eip1559") {
return a.maxFeePerGas > b.maxFeePerGas;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare priority fee before skipping replacement bump

When a previous EIP-1559 push is still pending, replacement pricing has to keep the priority-fee cap bumped as well as the max-fee cap. In the case where the latest estimate has a higher maxFeePerGas but a lower maxPriorityFeePerGas than the prior attempt (for example base fee rises while the suggested tip drops), this check returns false and the code sends the replacement with the lower tip, so clients can reject it as underpriced and the nonce can remain stuck until estimates happen to rise again. Please treat either EIP-1559 component needing a bump as higher, or otherwise preserve/bump the previous priority fee when reusing the nonce.

Useful? React with 👍 / 👎.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 No test coverage for new EIP-1559 gas escalation logic

This PR introduces significant new functionality (EIP-1559 gas parameter handling with escalation, capping, static overrides, and type discrimination) but includes no tests. The apps/price_pusher directory has no test files at all. The escalation logic in particular (escalateGas, capGas, gasIsHigherThan, applyStaticOverride) has multiple code paths and edge cases that would benefit from unit tests — especially the interaction between these functions when a pending transaction exists. Per REVIEW.md: "New functionality should have tests."

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 299 to 310
if (this.lastPushAttempt !== undefined) {
if (this.lastPushAttempt.nonce <= lastExecutedNonce) {
this.lastPushAttempt = undefined;
} else {
gasPriceToOverride =
this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier;
const escalated = this.escalateGas(
this.lastPushAttempt.gas,
this.overrideGasPriceMultiplier,
);
if (this.gasIsHigherThan(escalated, gas)) {
gas = this.capGas(escalated, gas);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 EIP-1559 escalation logic ignores maxPriorityFeePerGas, causing stuck replacement transactions

gasIsHigherThan at line 227 only compares maxFeePerGas when deciding whether to use the escalated gas or the fresh gas. When network conditions change such that fresh maxFeePerGas is higher (base fee increased) but fresh maxPriorityFeePerGas is lower (priority fee dropped), the code uses the fresh gas with inadequate priority fee. This fails to replace the pending transaction (nodes require BOTH fields bumped by ~10%). Critically, lastPushAttempt at line 326-329 is updated with this low-priority-fee gas BEFORE the try block, so subsequent escalations start from the low base. The cap at apps/price_pusher/src/evm/evm.ts:212-215 limits priority fee to fresh * cap (e.g., 2 * 5 = 10), which may never reach the replacement threshold (e.g., 10 * 1.1 = 11 needed). The pusher gets permanently stuck until the mempool expires the pending transaction.

Scenario trace showing stuck state

Push 1: sends tx with maxPriorityFeePerGas=10, lands in mempool.
Push 2: fresh gas has maxFeePerGas=60 > escalated 55, so fresh is used with maxPriorityFeePerGas=2. Fails: "replacement underpriced" (need >=11). lastPushAttempt stores priorityFee=2.
Push 3+: escalation starts from 2, cap is 2*5=10, but 10 < 11 needed. Permanently stuck.

Prompt for agents
The problem is in the escalation logic at lines 299-310 of apps/price_pusher/src/evm/evm.ts. When gasIsHigherThan(escalated, gas) returns false because fresh maxFeePerGas is higher, the code uses fresh gas without ensuring maxPriorityFeePerGas is sufficient for replacement.

The fix should ensure that when a pending transaction exists (lastPushAttempt not cleared), the maxPriorityFeePerGas is always at least as high as the escalated value, even when fresh maxFeePerGas is higher. One approach:

1. After the existing if/else block (lines 299-310), add logic for EIP-1559 mode that takes the max of each field independently:
   - maxFeePerGas = max(fresh.maxFeePerGas, escalated.maxFeePerGas) 
   - maxPriorityFeePerGas = max(fresh.maxPriorityFeePerGas, escalated.maxPriorityFeePerGas)
   Then apply the cap to the result.

2. Alternatively, modify gasIsHigherThan to return true if EITHER field of the escalated params exceeds fresh, then in capGas take the max of each field independently before applying the cap.

The key insight is that for EIP-1559 replacement transactions, both maxFeePerGas AND maxPriorityFeePerGas need to be bumped. The two values should be escalated independently rather than choosing one set of params wholesale based on a single comparison.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +52 to +59
legacy: {
default: false,
description:
"Use legacy (type-0) transactions with gasPrice instead of EIP-1559 (type-2). " +
"Enable this for chains that don't support EIP-1559.",
required: false,
type: "boolean",
} as Options,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Default behavior changes from legacy gasPrice to EIP-1559 without explicit opt-in

The legacy CLI flag defaults to false (apps/price_pusher/src/evm/command.ts:53), meaning existing deployments that upgrade will silently switch from using gasPrice (old behavior at the base branch) to using maxFeePerGas/maxPriorityFeePerGas. While most modern EVM chains support EIP-1559, chains that don't (and previously worked with the old code's gasPrice field) will break without adding --legacy. The old code always used the gasPrice field for all chains. This is a breaking change in default behavior for the package.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant