Conversation
…ting state InvariantTestYS Rule 14/15 (startVestingTime <= endVestingTime) started failing in nightly-fuzz once main advanced to include the YS system. Root cause is the acknowledged Finding 1: setFirstDepositTimestamp() moves startVestingTime without clearing a stale endVestingTime, reachable only when a vault hits totalSupply == 0 while a vest is still active and is then re-entered. That state is unreachable in production (deprecated vaults never fully drain, let alone mid-vest) and the source is intentionally WONTFIX. The prior handler workaround let the bad state happen then repaired it with a best-effort vestYield that reverts under the contract's own guards, so it silently left the invalid state and the invariant caught it — a flaky failure. Replace repair with prevention: the YS withdraw handlers now refuse to drain the vault to zero while vestingGains > 0 (vestYield already refuses to vest into an empty vault), so totalSupply == 0 always implies vestingGains == 0 and Rule 14/15 cannot be violated without weakening what they check. Remove the now-dead repair function from both the Foundry and Medusa handlers and document the rationale in the fuzzing README. Verified: FOUNDRY_PROFILE=nightly forge test --match-path test/fuzzing/InvariantTestYS.sol --fuzz-seed 0x2 -> 51 passed, 0 failed (previously 2 failed on the same seed and depth). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…guard test(fuzzing): guard YS invariant against unreachable empty-while-vesting state
Greptile SummaryThe PR replaces a best-effort post-deposit repair with a preventive YS withdrawal guard intended to keep the vault nonempty during active vesting.
Confidence Score: 4/5The PR appears safe to merge, with a non-blocking fuzz-coverage gap for final withdrawals after vesting has fully accrued. The production contracts are unchanged and the new guard prevents the targeted invalid state, but checking stored gains before exchange-rate realization can cause both fuzz withdrawal handlers to skip the only operation capable of draining a one-share vault after vesting ends. Files Needing Attention: test/fuzzing/handlers/TellerHandler.sol Important Files Changed
Reviews (1): Last reviewed commit: "Merge pull request #771 from Veda-Labs/t..." | Re-trigger Greptile |
| (, uint128 vestingGains, , , ) = accountantHandler.accountantYS().vestingState(); | ||
| if (vestingGains == 0) return shareAmount; | ||
| uint256 supply = vaultYS.totalSupply(); | ||
| if (shareAmount < supply) return shareAmount; // other holders keep the vault non-empty | ||
| return supply > 1 ? supply - 1 : 0; // leave 1 wei of shares as dust, or skip entirely |
There was a problem hiding this comment.
Post-vest withdrawals remain clamped
After the vesting end time, vestingGains remains nonzero until an exchange-rate update, but this guard runs before the Teller performs that update. A vault with one remaining share therefore returns early without realizing the completed vest, preventing both fuzz handlers from covering the documented post-vest full-drain path.
| (, uint128 vestingGains, , , ) = accountantHandler.accountantYS().vestingState(); | |
| if (vestingGains == 0) return shareAmount; | |
| uint256 supply = vaultYS.totalSupply(); | |
| if (shareAmount < supply) return shareAmount; // other holders keep the vault non-empty | |
| return supply > 1 ? supply - 1 : 0; // leave 1 wei of shares as dust, or skip entirely | |
| (, uint128 vestingGains, , , uint64 endVestingTime) = accountantHandler.accountantYS().vestingState(); | |
| if (vestingGains == 0 || block.timestamp >= endVestingTime) return shareAmount; | |
| uint256 supply = vaultYS.totalSupply(); | |
| if (shareAmount < supply) return shareAmount; // other holders keep the vault non-empty | |
| return supply > 1 ? supply - 1 : 0; // leave 1 wei of shares as dust, or skip entirely |
Knowledge Base Used: CI, Testing, and Fuzzing
What
Wholesale sync of
devintomain.mainwas last synced fromdevon 2026-07-31 (PR #765), so the only delta since is:test(fuzzing): guard YS invariant against unreachable empty-while-vesting stateContent diff is exactly those 4 test-harness files (+52 / −150); no source changes, no conflicts.
Why
The
nightly-fuzzworkflow runs amain+devmatrix on the same commit.devis now green on both nightlies;mainis still red on thenightly-fuzzYS invariant (InvariantTestYSRule 14/15) because it lacks #771. Merging brings the fix tomainso themainleg clears.Nightly status (2026-08-03 runs)
devlegmainlegScope of #771 (recap)
Test-only. The withdraw fuzz handlers now refuse to drain the YS vault to zero while a vest is active (an unreachable-in-production state), replacing a flaky best-effort repair. The underlying source Finding 1 remains intentionally WONTFIX. Verified at the failing nightly seed:
FOUNDRY_PROFILE=nightly forge test --match-path test/fuzzing/InvariantTestYS.sol --fuzz-seed 0x2→ 51 passed / 0 failed.After merge, the next
nightly-fuzzmainleg is expected green.🤖 Generated with Claude Code