Files
uupl-smart-contract/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-brief.md
T
golemandClaude Opus 5 fa36215def docs: track superpowers working documents in git
The uups-bank-demo wave's SDD records (ledger, six task briefs and reports,
review diffs) and the brainstorm design mockups were git-ignored, so they
existed only on one sandbox VM and reached no remote — this repo had no remote
at all until now.

Removes `.superpowers/` from .gitignore and the `*` .gitignore the superpowers
plugin writes inside .superpowers/sdd/; the second blocks the directory even
with the first removed.

Excluded as ephemeral local-server state, and now ignored by name:
.last-port, .last-token (a 64-char session token for a brainstorm server on a
port that is long gone), and the per-session state/ directories.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnwzj6McD6kSkXwjUKFKxe
2026-08-20 14:38:00 -06:00

73 lines
2.9 KiB
Markdown

### 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"
```
---