### Task 5: Prove V1 ledger conservation and solvency with stateful invariants **Files:** - Create: `test/helpers/BankHandler.sol` - Create: `test/BankInvariant.t.sol` **Interfaces:** - Consumes `MockUSDC`, the V1 proxy, and a bounded set of four actor addresses. - Produces independent ghost counters and handler accessors used by invariant assertions. - Later Task 11 extends the same handler with V2 transfers. - [ ] Write `BankHandler` with four immutable actor addresses and only these V1 actions: - `deposit(uint256 actorSeed, uint256 amount)`: select an actor, bound amount to `[1, 10_000e6]`, mint to the actor, approve, deposit, and increment `ghostDeposited`. - `withdraw(uint256 actorSeed, uint256 amount)`: select an actor, return early only when its internal balance is zero, bound to `[1, balance]`, withdraw, and increment `ghostWithdrawn`. - `donate(uint256 actorSeed, uint256 amount)`: mint and directly transfer `[1, 1_000e6]` to the proxy, incrementing `ghostDonated` but not liabilities. Every action uses `vm.startPrank(actor)`/`vm.stopPrank()` in a balanced scope. The handler exposes the actor array so the invariant test, not the handler, sums on-chain balances. In invariant setup, transfer `MockUSDC` ownership to the handler so only the handler can mint bounded test liquidity. This avoids impersonating an owner inside actions and does not affect bank ownership. - [ ] Write failing invariant tests: ```solidity function invariant_liabilitiesEqualTrackedBalances() public view { uint256 sum; for (uint256 i; i < handler.actorCount(); ++i) { sum += bank.balanceOf(handler.actorAt(i)); } assertEq(sum, bank.totalLiabilities()); } function invariant_reservesCoverLiabilities() public view { assertGe(token.balanceOf(address(bank)), bank.totalLiabilities()); } function invariant_ghostAccountingMatchesChain() public view { assertEq(handler.ghostDeposited() - handler.ghostWithdrawn(), bank.totalLiabilities()); assertEq( handler.ghostDeposited() + handler.ghostDonated() - handler.ghostWithdrawn(), token.balanceOf(address(bank)) ); } ``` - [ ] Run red: ```bash npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force ``` Expected red: handler implementation is absent/incomplete. - [ ] Implement the minimum handler. Register it with `targetContract(address(handler))` and explicitly target only its three action selectors so inherited test/helper functions cannot enter the action space. - [ ] Run green and retain the call summary in output: ```bash npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force ``` Expected: all three invariants pass for `128` runs at depth `64`; the summary shows deposits, withdrawals, and donations were each exercised. - [ ] Commit: ```bash git add test/helpers/BankHandler.sol test/BankInvariant.t.sol git commit -m "test: prove V1 accounting invariants" ``` ---