Security property violated
Solvency of the withdrawal pool. Rewards must never be paid from balances that back user principal.
Mechanics
claim() pays rewards from the same pool that holds deposits. A claimant leaves with principal plus rewards; the remaining depositors inherit the deficit.
Controlled exploit
function test_exploit_rewardsFromPrincipal() public {
_deposit(alice, 100e18);
_deposit(mallory, 100e18);
vm.warp(block.timestamp + 30 days);
vm.prank(mallory);
vault.claimAndWithdraw(); // leaves with 100e18 + rewards
assertLt(vault.totalAssets(), vault.totalPrincipal());
}
Negative control
Honest claim without withdrawal: pool remains solvent. Control passed — the effect is specific to the sequence, not to the harness.
Peer-review verdict
Independent refutation attempted — failed. Reproduced 3/3 under identical pinned state. Alternative explanations (rounding artefact, harness bias) rejected.
Measured impact
attacker +12.4% of pool · users −12.4% of pool · protocol 0. Deficit is permanent until the pool is recapitalised.
Evidence hash
sha256:8f3a19c2e7…b04d6c91e
Remediation direction
Separate the reward reservoir from principal accounting. Pay rewards from a dedicated balance and revert when it is exhausted.
Required regression test
test_invariant_principalNeverFundsRewards() — must hold across arbitrary deposit / claim / withdraw sequences (fuzzed).