fix(executor): preserve 2-byte 0x7702 EIP-7702 marker when packing initCode - #8
Conversation
There was a problem hiding this comment.
This PR fixes a bug where the EIP-7702 0x7702 marker in a UserOperation's factory field was being right-padded to 20 bytes during packing, invalidating paymaster signatures that commit to keccak256(initCode) and causing bundle-time AA34 reverts. The fix preserves the 2-byte marker as-is and adds unit tests covering normal factory addresses, the EIP-7702 case, and edge cases.
One thing the inline comments don't make obvious: the corresponding unpackUserOp function has a symmetrical gap — it still blindly slices 20 bytes for the factory address, which will misparse (or error on) any packed op using the 2-byte EIP-7702 marker, breaking the pack→unpack round-trip.
✅ No blocking issues found by the automated review — this is not a substitute for human review.
Problem
EIP-7702 userOps that carry a paymaster are accepted by
eth_sendUserOperationand then silently deleted at bundle time ("failed on chain estimation. deleting..."), never mining. Self-funded 7702 ops and non-7702 paymaster ops are unaffected.Root cause
packUserOpright-padsop.factoryto 20 bytes unconditionally (packedUserOpUtils.ts), turning the EIP-7702 marker0x7702into0x7702+ 18 zero bytes. Paymasters that commit tokeccak256(userOp.initCode)— e.g. Pimlico's singleton (SingletonPaymasterV7.sol:356, inherited by V8) — signed over the canonical 2-byte form, so the re-packed op fails paymaster signature validation withFailedOp("AA34 signature error")insidevalidateBundle'seth_estimateGas, and the op is cancelled without any client-visible error.The account signature is unaffected because the EntryPoint normalizes initCode in its own userOpHash for 7702 ops (
UserOperationLib.sol:48) — which is why only the paymaster combination dies (always AA34, never AA24).Evidence
0x04d50f8c…deleted withFailedOp(0, "AA34 signature error")from the bundle estimation.handleOpsestimation with 2-byte initCode passes (0x615b3gas); identical op with the 20-byte padded initCode revertsAA34 signature error.Fix
Guard the pad with the exported
INITCODE_EIP7702_MARKERconstant (case-folded). Real 20-byte factory addresses take the identical path as before (the pad was a no-op for them).Tests
New
packedUserOpUtils.test.ts(5 cases): marker stays 2 bytes, odd-cased marker, marker + factoryData concatenation, real factory address unchanged, no factory →0x. The 7702 cases were written first and observed failing with the padded output before the fix (TDD).tscbuild clean; the executor suite's 8 pre-existing failures (anvil-dependent fixtures) are identical on cleandevelop.Note
Upstream
etherspot/skandhamasterhas the same unconditional padding (hexRightPad(op.factory ?? "0x", 20)), so this is worth an upstream issue/PR as well.🤖 Generated with Claude Code
https://claude.ai/code/session_01SDWq76gdRTBEsNF6u6yJq7