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
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Review package: 2a21766237a9c29970519f7fa00482faecd06fdd..6dcbb03f3c79979416c2f0dd37556539276a4e29
|
||||
|
||||
## Commits
|
||||
6dcbb03 test: prove V1 accounting invariants
|
||||
|
||||
## Files changed
|
||||
test/BankInvariant.t.sol | 43 ++++++++++++++++++++++++
|
||||
test/helpers/BankHandler.sol | 79 ++++++++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 122 insertions(+)
|
||||
|
||||
## Diff
|
||||
diff --git a/test/BankInvariant.t.sol b/test/BankInvariant.t.sol
|
||||
new file mode 100644
|
||||
index 0000000..f699902
|
||||
--- /dev/null
|
||||
+++ b/test/BankInvariant.t.sol
|
||||
@@ -0,0 +1,43 @@
|
||||
+// SPDX-License-Identifier: MIT
|
||||
+pragma solidity 0.8.35;
|
||||
+
|
||||
+import {BankTestBase} from "./helpers/BankTestBase.sol";
|
||||
+import {BankHandler} from "./helpers/BankHandler.sol";
|
||||
+
|
||||
+contract BankInvariantTest is BankTestBase {
|
||||
+ BankHandler internal handler;
|
||||
+
|
||||
+ function setUp() public override {
|
||||
+ super.setUp();
|
||||
+
|
||||
+ handler = new BankHandler(token, bank);
|
||||
+ vm.prank(owner);
|
||||
+ token.transferOwnership(address(handler));
|
||||
+
|
||||
+ targetContract(address(handler));
|
||||
+ bytes4[] memory selectors = new bytes4[](3);
|
||||
+ selectors[0] = handler.deposit.selector;
|
||||
+ selectors[1] = handler.withdraw.selector;
|
||||
+ selectors[2] = handler.donate.selector;
|
||||
+ targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
|
||||
+ }
|
||||
+
|
||||
+ 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))
|
||||
+ );
|
||||
+ }
|
||||
+}
|
||||
diff --git a/test/helpers/BankHandler.sol b/test/helpers/BankHandler.sol
|
||||
new file mode 100644
|
||||
index 0000000..20ec183
|
||||
--- /dev/null
|
||||
+++ b/test/helpers/BankHandler.sol
|
||||
@@ -0,0 +1,79 @@
|
||||
+// SPDX-License-Identifier: MIT
|
||||
+pragma solidity 0.8.35;
|
||||
+
|
||||
+import {Test} from "forge-std/Test.sol";
|
||||
+
|
||||
+import {BankV1} from "../../src/BankV1.sol";
|
||||
+import {MockUSDC} from "../../src/MockUSDC.sol";
|
||||
+
|
||||
+contract BankHandler is Test {
|
||||
+ MockUSDC internal immutable token;
|
||||
+ BankV1 internal immutable bank;
|
||||
+
|
||||
+ address internal immutable actor0;
|
||||
+ address internal immutable actor1;
|
||||
+ address internal immutable actor2;
|
||||
+ address internal immutable actor3;
|
||||
+
|
||||
+ uint256 public ghostDeposited;
|
||||
+ uint256 public ghostWithdrawn;
|
||||
+ uint256 public ghostDonated;
|
||||
+
|
||||
+ constructor(MockUSDC token_, BankV1 bank_) {
|
||||
+ token = token_;
|
||||
+ bank = bank_;
|
||||
+ actor0 = address(0x1001);
|
||||
+ actor1 = address(0x1002);
|
||||
+ actor2 = address(0x1003);
|
||||
+ actor3 = address(0x1004);
|
||||
+ }
|
||||
+
|
||||
+ function deposit(uint256 actorSeed, uint256 amount) external {
|
||||
+ address actor = actorAt(actorSeed % actorCount());
|
||||
+ amount = bound(amount, 1, 10_000e6);
|
||||
+ token.mint(actor, amount);
|
||||
+
|
||||
+ vm.startPrank(actor);
|
||||
+ token.approve(address(bank), amount);
|
||||
+ bank.deposit(amount);
|
||||
+ vm.stopPrank();
|
||||
+
|
||||
+ ghostDeposited += amount;
|
||||
+ }
|
||||
+
|
||||
+ function withdraw(uint256 actorSeed, uint256 amount) external {
|
||||
+ address actor = actorAt(actorSeed % actorCount());
|
||||
+ uint256 balance = bank.balanceOf(actor);
|
||||
+ if (balance == 0) return;
|
||||
+
|
||||
+ amount = bound(amount, 1, balance);
|
||||
+ vm.startPrank(actor);
|
||||
+ bank.withdraw(amount);
|
||||
+ vm.stopPrank();
|
||||
+
|
||||
+ ghostWithdrawn += amount;
|
||||
+ }
|
||||
+
|
||||
+ function donate(uint256 actorSeed, uint256 amount) external {
|
||||
+ address actor = actorAt(actorSeed % actorCount());
|
||||
+ amount = bound(amount, 1, 1_000e6);
|
||||
+ token.mint(actor, amount);
|
||||
+
|
||||
+ vm.startPrank(actor);
|
||||
+ token.transfer(address(bank), amount);
|
||||
+ vm.stopPrank();
|
||||
+
|
||||
+ ghostDonated += amount;
|
||||
+ }
|
||||
+
|
||||
+ function actorCount() public pure returns (uint256) {
|
||||
+ return 4;
|
||||
+ }
|
||||
+
|
||||
+ function actorAt(uint256 index) public view returns (address) {
|
||||
+ if (index == 0) return actor0;
|
||||
+ if (index == 1) return actor1;
|
||||
+ if (index == 2) return actor2;
|
||||
+ return actor3;
|
||||
+ }
|
||||
+}
|
||||
Reference in New Issue
Block a user