test(invariants): totalSupply tracks net mints across mint/burn cycles (closes #25)#39
Merged
Merged
Conversation
closes #25) Adds forge-test/invariants/StablecoinInvariants.t.sol with a Handler that bounds fuzz inputs to legal mint/burn/burnFrom sequences against the Stablecoin and ghost-tracks cumulative mint/burn. The handler seeds three actors so burn paths exercise from call 1; mint caps at 1e30 wei to avoid uint overflow over the default invariant budget. Asserts two invariants: 1. coin.totalSupply() == ghostMinted - ghostBurned (the core ledger) 2. sum of per-actor balances == totalSupply (no balance leaks) A third trivial invariant exposes per-action call counts at -vv so a run that minted but never burned is detectable in CI output. Catches the accounting drift class: e.g. a regression where _mint emits the right event but skips the balance write (totalSupply increases but sum of balances does not), which unit tests miss because they always mint+burn at known endpoints, not random sequences.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Adds the first invariant-style test for
Stablecoin.sol. AHandlercontract bounds fuzz inputs to legal sequences ofmint/burn/burnFromagainst three pre-seeded actors, and ghost-tracks cumulative mints and burns. The invariant then asserts that on-chaintotalSupplyalways equals the net ghost mints minus burns.Closes #25.
Why invariants here
Unit tests in
Stablecoin.t.solalready cover mint/burn at known endpoints. What they miss: any regression that causestotalSupplyto drift from the sum of balances across random sequences. Examples this catches that unit tests don't:_mintthat emits the right event but skips the balance writeburnFrompath that decrements balance twice in some edge caseWhat's asserted
totalSupply() == ghostMinted - ghostBurnedsum of per-actor balances == totalSupplyinvariant_callSummary(visibility)-vvso a run that minted-but-never-burned is detectable in CIHandler design
targetContract(address(handler))+ explicittargetSelectorlist — restricts fuzzing tohandler_mint/handler_burn/handler_burnFrom. Without that, the fuzzer wanders intopause(),grantRole(), etc. and most calls revert in setUp, eating the budget.0xB1,0xB2,0xB3) so burn paths fire from call 1.[0, 1e30]— generous headroom (1e24INV at 6 decimals) while preventinguint256overflow at the high tail of the run × depth budget.Verifying it catches bugs
Per acceptance criteria, the test must fail when accounting is intentionally broken. To verify locally:
contracts/Stablecoin.sol, change_mint(to, amount)inmint()to_mint(to, amount + 1)(introduce drift).forge test --mt invariant_totalSupplyMatchesNetMints -vvv.Stablecoin accounting drift: totalSupply != net mints.Test plan
forge test -vvvjob passes (forge-test/invariants is underFOUNDRY_TESTalready)-vvshows non-zero counts in all three call types (mint / burn / burnFrom)