fix(security): H-01 — chunked retryPendingDebt + audit findings docs#227
Merged
Conversation
…yer x402 paths confirmed separate at TA layer
…eTxLimit) retryPendingDebt always retried the FULL pending balance, so once the accumulator exceeded the token's per-tx limit (5000 aPNTs) recordDebt reverted forever and the debt could only be written off via clearPendingDebt. Now takes an explicit (clamped to the balance) so a large balance is recovered in chunks over multiple owner calls. Reuses recordDebt + the existing pendingDebts accumulator — no new state. - Test: test_RetryPendingDebt_Chunked (accumulate → drain in 2 chunks → empty reverts). - Ops runbook: docs/operations/pending-debt-runbook.md (management-backend retry rule). forge test 970 passed / 0. SuperPaymaster 24,159 bytes (EIP-170 OK, 417 spare).
Stale callers used the old retryPendingDebt(address,address) selector after H-01 added the chunk `amount` param. test-group-B5 now passes amount=0 (full balance) and the ABI JSON is regenerated. Flagged by the stop-time review gate.
fanhousanbu
approved these changes
Jun 1, 2026
Contributor
fanhousanbu
left a comment
There was a problem hiding this comment.
✅ APPROVE — H-01 chunked retryPendingDebt
Logic verified:
function retryPendingDebt(address token, address user, uint256 amount) external onlyOwner nonReentrant {
uint256 pending = pendingDebts[token][user];
if (pending == 0) revert NoPendingDebt();
if (amount == 0 || amount > pending) amount = pending; // clamp
pendingDebts[token][user] = pending - amount; // safe: amount ≤ pending
IxPNTsToken(token).recordDebt(user, amount);
emit PendingDebtRetried(token, user, amount);
}- Clamp on entry (not after): prevents over-retry and handles the
amount == 0→ "full balance" shortcut cleanly. ✅ NoPendingDebtcheck on the pre-clamppendingvalue → correct (ifamount == 0would be clamped topending, you'd still want to revert whenpending == 0). ✅pendingDebtsupdated BEFORErecordDebtexternal call. ThenonReentrantguard is present, and the write-before-external-call ordering is correct anyway (CEI). ✅- Event carries actual retried amount (post-clamp), not the input. ✅
Operability: If recordDebt itself reverts (e.g., the amount chunk still exceeds xPNTs maxSingleTxLimit), the pendingDebts update is rolled back atomically. The caller must retry with a smaller chunk. The pending-debt runbook should document the chunk sizing strategy. This is by design.
Breaking change: 2-arg → 3-arg signature. In-repo E2E callers updated in the same PR. ✅
All 4 fixes are independent, minimal, and correctly address the attack vectors. Stack ready to merge in order: #225 → #226 → #228 → #227.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked PR 4/4 (merge after C-02/C-03). Base:
sec/c02-c03-x402-auth.Fixes H-01:
retryPendingDebtalways retried the full pending balance, so a balance > the token'smaxSingleTxLimitreverted forever (onlyclearPendingDebtwrite-off remained). Now takes an explicitamount(clamped) so a large balance is recovered in chunks. Reuses existingrecordDebt+pendingDebts. Ops runbook:docs/operations/pending-debt-runbook.md. Also lands the final findings-resolution + KMS #16 consistency docs.forge test970 green.