What it is: the canonical ERC-4337
EntryPointcharges a paymaster forpostOp-phase execution above thepaymasterPostOpGasLimitit declared — the value EIP-4337 §4.3.1 defines as a strict upper bound. The overcharge occurs even when the paymaster'spostOpis empty andpaymasterPostOpGasLimit = 0.Proven: deterministic Foundry PoC against the real
EntryPointon a mainnet fork (0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f). 13/13 tests pass.
- Spec: EIP-4337 §4.3.1 — a paymaster's
postOpgas is bounded above bypaymasterPostOpGasLimit; the paymaster must never pay forpostOpbeyond it. - Reality: on a
postOpOOG revert,EntryPointstill bills the paymaster for internal overhead surrounding thepostOpcall (context handling + call machinery) — a nonzero charge with the limit set to 0. - Consequence: a paymaster's declared gas budget is not the real ceiling. A party that controls op inclusion (a malicious/colluding bundler) can force a sponsored op into a
postOpOOG path and make the paymaster overpay, per op, every block, bundled N-at-a-time. - Severity framing (honest): this is a spec-conformance + griefing/overcharge bug, not a one-transaction paymaster drain. The per-op overcharge is small (~1.18e14 wei measured); the risk is systematic — it breaks the gas-accounting invariant every paymaster relies on and bleeds deposits at scale.
An ERC-4337 paymaster sponsors a user operation and optionally runs postOp() after the account call. When it does, it declares paymasterPostOpGasLimit inside paymasterAndData. EIP-4337 §4.3.1 treats this as a strict upper bound: the paymaster may be charged for at most that much postOp gas.
Paymasters rely on this to price sponsorship. If the real charge can exceed the declared limit, every paymaster's accounting is off by an amount it never agreed to — including paymasters that run no postOp logic at all (limit = 0).
When postOp reverts out-of-gas, EntryPoint's post-execution accounting still attributes the surrounding internal overhead (copying the paymaster context, the call frame, bookkeeping) to the paymaster's bill, without capping that attribution at paymasterPostOpGasLimit.
Result, measured with an empty postOp and paymasterPostOpGasLimit = 0:
Perfect Paymaster Deposit Before: 2000000000000000000 wei
Perfect Paymaster Deposit After: 1999881513781656571 wei
Overcharge (this op): 118486218343429 wei (~0.000118 ETH)
CONFIRMED ON MAINNET FORK with empty postOp, limit = 0.
The declared ceiling was 0. The paymaster paid > 0. That is the violation.
sequenceDiagram
participant B as Bundler (attacker / colluder)
participant EP as EntryPoint (mainnet)
participant PM as Paymaster (limit = 0)
B->>EP: handleOps([ userOp sponsored by PM ])
EP->>PM: validatePaymasterUserOp -> context, postOpGasLimit = 0
EP->>EP: execute account call
EP->>PM: postOp() (empty) -> OOG revert
Note over EP,PM: 4.3.1 bound = 0, so charge must be 0
EP->>PM: charge deposit for internal postOp overhead (> 0)
B->>EP: repeat, bundled N ops per tx, every block
- What's exposed: paymaster deposits held in the EntryPoint. This is not "drainable in one transaction"; the per-op overcharge is ~1.18e14 wei.
- Why it still matters:
- It is a provable spec violation (§4.3.1) in the reference
EntryPointused across the ecosystem. - The charge is repeatable and bundlable — a
50-op bundle multiplies it ~50x per transaction, and it recurs every block. - It breaks the invariant paymasters price against, so a sponsor's cost model silently under-estimates real spend.
- It is a provable spec violation (§4.3.1) in the reference
- Who can trigger it: anyone who controls op inclusion — a malicious or colluding bundler — using a legitimately sponsored op steered into a
postOpOOG path. It does not require the attacker to forgepaymasterAndData(see the Pimlico exchange below).
Cap the postOp-phase gas attributed to the paymaster at the declared limit, so internal overhead can never be billed beyond paymasterPostOpGasLimit:
// concept: clamp the charged postOp gas to the declared upper bound
uint256 chargedPostOpGas = Math.min(actualPostOpGasUsed, paymasterPostOpGasLimit);test/ExploitForkTestFixed.sol demonstrates the patched path: with the overhead capped, the overcharge collapses and the drain is no longer profitable.
➡️ Fix PR: Tejanadh/account-abstraction#1
forge test -vvv # full suite, 13/13 pass on fork
forge test --match-path test/MainnetEmptyPostOpTest.sol -vvv # empty-postOp overcharge on mainnet fork
forge test --match-path test/Safe4337OverchargeTest.t.sol -vvv # same bug via the Safe 4337 Module (v0.3.0)
forge test --match-path test/ExploitForkTestFixed.sol -vvv # patched path - overcharge collapses| Test | Shows |
|---|---|
MainnetEmptyPostOpTest.sol |
overcharge with empty postOp, limit = 0, on a mainnet fork |
PerfectPaymasterTest.sol |
overcharge against a minimal, well-behaved paymaster |
BundleDrainTest.sol |
~50x amplification via a bundled handleOps |
Safe4337OverchargeTest.t.sol |
the bug reaches the Safe 4337 Module ecosystem |
ExploitForkTestFixed.sol |
the capped-overhead fix removes profitability |
Reported to: HackenProof (Account Abstraction program) · Cantina (via Alchemy) · Safe Bug Bounty.
Pimlico exchange (kept for transparency). Pimlico assessed their paymaster as not affected, because users cannot set paymasterAndData (it is returned by their off-chain API and signature-checked). That mitigation is real for user-initiated manipulation.
Counterpoint: the underlying EntryPoint overcharge is independent of who authored paymasterAndData. A malicious bundler can include a legitimately Pimlico-sponsored op known to hit a postOp OOG revert; the EntryPoint then overcharges the paymaster for internal overhead even though the context was generated and signed by Pimlico. Offer to adapt the PoC to a specific paymaster implementation stands.
I report this as a spec-conformance / paymaster-overcharge issue. It is high-confidence and provable, with a bounded per-op economic impact amplified by bundling and repetition — I state that bound explicitly rather than headline the paymaster's total TVL. Programs that received it may grade it differently; the technical claim and its limits are exactly as measured above.
Contact: tejanadh927@gmail.com · @TEJANadh10 · audit portfolio