Summary
During validatePaymasterUserOp, _checkSolidarityAccess checks solidarity.balance >= maxCost but does not reserve (deduct) the amount. This means multiple UserOps in the same ERC-4337 bundle can all validate against the same solidarity.balance, even though sequential postOp deductions will drain it.
Affected Paths
- Grace-period orgs:
_checkSolidarityAccess line 524 checks solidarity.balance < maxCost without deducting
- Post-grace orgs:
_checkSolidarityAccess line 546 checks solidarity.balance < requiredSolidarity without deducting
- Onboarding ops:
_validateOnboardingEligibility line 1633 checks solidarity.balance < maxCost without deducting
In contrast, budget (_checkBudget) and org balance (_checkOrgBalance) both do reserve during validation — this was added in the C-1/C-3 security fixes.
Impact
When multiple ops for the same org (or multiple onboarding ops) are packed in one bundle:
- All
validatePaymasterUserOp calls see the same solidarity.balance and pass
- In
postOp, each op deducts from solidarity sequentially
- When solidarity runs out mid-bundle,
_updateOrgFinancials reverts with InsufficientFunds
- EntryPoint retries with
postOpReverted, and _postOpFallback handles it by charging deposits
For grace orgs with zero deposits, the fallback creates phantom debt (org.spent > org.deposited). This is bounded by maxSpendDuringGrace — subsequent validation calls will hit GracePeriodSpendLimitReached.
Practical exposure is low because:
maxSpendDuringGrace caps total grace spending (~0.01 ETH)
- Gas caps bound per-op
maxCost
- Phantom debt on zero-balance accounts is bookkeeping only
- The fallback increments
solidarityUsedThisPeriod, blocking future validation
Why This Wasn't Fixed with C-1/C-3
Solidarity is a shared global resource, not org-scoped. Reserving it during validation would:
- Create artificial contention between unrelated orgs in the same bundle — org A's reservation reduces availability for org B
- Require threading a
reservedSolidarity amount through context and unreserving in all three postOp paths (grace, post-grace, onboarding)
- Increase gas cost for every op (~5k gas per SSTORE) for a shared slot that would become a contention point
The fix complexity is high relative to the bounded, low-impact exposure. Should be evaluated as a standalone improvement.
Suggested Approach
If fixing, consider:
- Reserve solidarity per-org (not globally) by tracking
solidarityReserved per org during validation
- Or accept the current behavior and document the bound: max phantom debt per org per period =
maxSpendDuringGrace (grace) or matchAllowance (post-grace)
Labels
Security, Enhancement, PaymasterHub
Summary
During
validatePaymasterUserOp,_checkSolidarityAccesscheckssolidarity.balance >= maxCostbut does not reserve (deduct) the amount. This means multiple UserOps in the same ERC-4337 bundle can all validate against the samesolidarity.balance, even though sequentialpostOpdeductions will drain it.Affected Paths
_checkSolidarityAccessline 524 checkssolidarity.balance < maxCostwithout deducting_checkSolidarityAccessline 546 checkssolidarity.balance < requiredSolidaritywithout deducting_validateOnboardingEligibilityline 1633 checkssolidarity.balance < maxCostwithout deductingIn contrast, budget (
_checkBudget) and org balance (_checkOrgBalance) both do reserve during validation — this was added in the C-1/C-3 security fixes.Impact
When multiple ops for the same org (or multiple onboarding ops) are packed in one bundle:
validatePaymasterUserOpcalls see the samesolidarity.balanceand passpostOp, each op deducts from solidarity sequentially_updateOrgFinancialsreverts withInsufficientFundspostOpReverted, and_postOpFallbackhandles it by charging depositsFor grace orgs with zero deposits, the fallback creates phantom debt (
org.spent > org.deposited). This is bounded bymaxSpendDuringGrace— subsequent validation calls will hitGracePeriodSpendLimitReached.Practical exposure is low because:
maxSpendDuringGracecaps total grace spending (~0.01 ETH)maxCostsolidarityUsedThisPeriod, blocking future validationWhy This Wasn't Fixed with C-1/C-3
Solidarity is a shared global resource, not org-scoped. Reserving it during validation would:
reservedSolidarityamount through context and unreserving in all three postOp paths (grace, post-grace, onboarding)The fix complexity is high relative to the bounded, low-impact exposure. Should be evaluated as a standalone improvement.
Suggested Approach
If fixing, consider:
solidarityReservedper org during validationmaxSpendDuringGrace(grace) ormatchAllowance(post-grace)Labels
Security, Enhancement, PaymasterHub