Skip to content

PaymasterHub: solidarity balance not reserved during validation (bundle safety gap) #123

@hudsonhrh

Description

@hudsonhrh

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:

  1. All validatePaymasterUserOp calls see the same solidarity.balance and pass
  2. In postOp, each op deducts from solidarity sequentially
  3. When solidarity runs out mid-bundle, _updateOrgFinancials reverts with InsufficientFunds
  4. 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:

  1. Create artificial contention between unrelated orgs in the same bundle — org A's reservation reduces availability for org B
  2. Require threading a reservedSolidarity amount through context and unreserving in all three postOp paths (grace, post-grace, onboarding)
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions