Skip to content

fix(L1/I1): request-slot auth + drain per-epoch redemption dust - #21

Merged
jayeshy14 merged 1 commit into
mainfrom
fix/vault-request-hygiene
Jul 20, 2026
Merged

fix(L1/I1): request-slot auth + drain per-epoch redemption dust#21
jayeshy14 merged 1 commit into
mainfrom
fix/vault-request-hygiene

Conversation

@jayeshy14

Copy link
Copy Markdown
Owner

Batch PR E of the CPPI vault audit remediation (follows PR A–D: revert-resilience #17, oracle-robustness #18, execution-mev-bounds #19, floor-correctness #20).

L1 — foreign controller-slot griefing DoS (Low)

requestDeposit/requestRedeem previously only authorized the caller against owner_ (whose assets move). The caller could still write an arbitrary controller's request slot, seeding a dust request there. Because a slot with a pending request from an earlier epoch reverts (PendingRequestFromEarlierEpoch), an attacker could block a victim controller's next deposit/redeem.

Fix: both entrypoints now also require the caller to be the controller or its operator (_authControllerOrOperator(controller)), so no one can write a foreign slot.

I1 — reserved-payout dust frozen forever (Informational)

At settlement the vault reserves floor(sumShares * price) aggregate, but each claimant is paid floor(userShares * price). With more than one fractional claimant the per-user floors sum to strictly less than the aggregate, leaving a few wei permanently stuck in totalReservedPayoutsWad — which is subtracted from shareholderNav, so it is lost to holders forever.

Fix: track reserved payout and outstanding redeem shares per epoch (epochReservedWad / epochRedeemRemaining). The last claimant of an epoch drains the residue back out of totalReservedPayoutsWad.

Tests

  • test_l1_cannotGriefForeignControllerSlot — attacker funding from self cannot seed a victim's slot; victim's slot still works.
  • test_l1_operatorCanStillWriteControllerSlot — a legitimate operator is unaffected.
  • test_i1_reservedDustDrainedOnLastClaim — three fractional holders redeem in one epoch, leaving 2 wei of rounding dust; asserts it is fully drained after the last claim. Verified to fail (2 != 0) when the drain branch is removed.

Full suite: 141 passing.

L1: requestDeposit/requestRedeem now require the caller to be the controller
or its operator, not just able to move owner_'s assets. This blocks a third
party from seeding a dust request into a foreign controller's slot to grief
its next deposit/redeem (PendingRequestFromEarlierEpoch DoS).

I1: track reserved payout and outstanding redeem shares per settlement epoch.
The aggregate payout reserved at settle is floor(sumShares * price) while each
claimant is paid floor(userShares * price); with multiple fractional claimants
the per-user floors sum to less than the aggregate, leaving a few wei frozen
in totalReservedPayoutsWad (and thus subtracted from shareholderNav forever).
The last claimant of an epoch now drains that residue.

Tests: two L1 griefing/operator cases and an I1 case that leaves 2 wei of
rounding dust and asserts it is fully drained after the last claim.
@jayeshy14
jayeshy14 merged commit 0b6df69 into main Jul 20, 2026
3 checks passed
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.

1 participant