Skip to content

audit: reentrancy + griefing red-team 2026-05-16#139

Open
tangletools wants to merge 1 commit into
mainfrom
chore/audit-reentrancy-griefing-2026-05-16
Open

audit: reentrancy + griefing red-team 2026-05-16#139
tangletools wants to merge 1 commit into
mainfrom
chore/audit-reentrancy-griefing-2026-05-16

Conversation

@tangletools
Copy link
Copy Markdown
Contributor

Summary

Read-only red-team audit of the reentrancy and griefing surfaces called out in the brief.

  • 3 medium / 2 low / 1 informational. No critical or high.
  • All token-moving entry points (fundService, withdrawRemainingEscrow, billing, claims, slashing, jobs, RFQ, quotes, lifecycle) confirmed nonReentrant. The OZ guard storage slot is ERC-7201 namespaced, so it is genuinely shared across diamond facets and the subscription billing self-call to distributeBillWithKeeper runs under the outer billSubscription guard. Operator and keeper payments are pull via _pendingRewards. The livelock escape (terminateServiceForNonPayment) is permissionless. CEI ordering holds on every withdrawal / claim / refund path.

Findings

M-1 Developer transfer in _distributeBill is not griefing-isolated. Same shape as the gap PR #136 closed for claimRewardsAll. Apply the _forwardStakerShare try/catch + refund-to-escrow pattern at PaymentsDistribution.sol:209.

M-2 withdrawRemainingEscrow has no fallback path when the escrow token is broken. No admin rescue exists. Either add an admin-rescue or let the owner specify an alternate recipient.

M-3 getNonPaymentTerminationPolicy is invoked without a gas cap. Uses try/catch instead of the gas-bounded raw staticcall pattern already used by _resolveBillAdjustmentBps. Same hygiene gap on querySlashingOrigin, getHeartbeatInterval, getHeartbeatThreshold. Most important on the livelock-escape path.

L-1 _settleDisputeBond strands the bond when the treasury push fails after executeSlash. Add an admin re-settle or fall back to a pull-credit on the treasury.

L-2 _operatorActiveSlashProposals cap check is ordered after SlashingLib.proposeSlash. Functionally correct, cosmetic.

I-1 Clarification: _claimRewardsTokenSafe's omission of nonReentrant is correct and confirmed against the OZ source.

Full report: docs/audits/REDTEAM-REENTRANCY-GRIEFING-2026-05-16.md

Test plan

  • Confirm M-1 fix matches _forwardStakerShare shape (refund to escrow, emit marker).
  • M-2 design choice (admin rescue vs owner-designated alternate recipient).
  • M-3: replace try/catch on view hooks with manager.staticcall{ gas: MANAGER_HOOK_GAS_LIMIT }(...).
  • L-1: add adminSettleStuckDisputeBond or fall through to _pendingDisputeBondRefunds[treasury].
  • L-2: move the cap increment + check above SlashingLib.proposeSlash.

3 medium / 2 low / 1 informational. No critical or high-severity issues.

Medium: `_distributeBill` push to BSM-resolved developer recipient can
be permanently griefed by a malicious manager; `withdrawRemainingEscrow`
has no fallback when the escrow token is broken; `getNonPaymentTerminationPolicy`
is invoked without a gas cap, letting a malicious BSM grief the livelock
escape.

Low: `_settleDisputeBond` strands dispute bonds on executed proposals
when the treasury push fails; `_operatorActiveSlashProposals` cap check
ordering is inverted (cosmetic).

Confirmed clean: every token-moving entry point holds `nonReentrant`;
the OpenZeppelin ReentrancyGuard storage slot is genuinely shared
across facets via ERC-7201; operator and keeper payments are pull via
`_pendingRewards`; self-call gates fire before any state read; CEI
ordering holds on every withdrawal / claim / refund path; the
subscription livelock escape (`terminateServiceForNonPayment`) is
permissionless.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants