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,96 @@
|
||||
### Task 4: Implement V1 custody accounting and adversarial unit tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/BankV1.sol`
|
||||
- Create: `test/BankV1.t.sol`
|
||||
- Create: `test/mocks/FeeOnTransferToken.sol`
|
||||
- Create: `test/mocks/ReentrantToken.sol`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces `deposit(uint256)` and `withdraw(uint256)` plus `Deposited`, `Withdrawn`, `ZeroAmount`, `InsufficientBalance`, and `UnexpectedAssetDelta`.
|
||||
- Consumes only a standard non-rebasing ERC-20 through `IERC20`/`SafeERC20`.
|
||||
- Preserves `reserves >= totalLiabilities`; direct token transfers may create surplus.
|
||||
|
||||
- [ ] Write event and error declarations into the test expectations before production code:
|
||||
|
||||
```solidity
|
||||
event Deposited(address indexed account, uint256 amount);
|
||||
event Withdrawn(address indexed account, uint256 amount);
|
||||
|
||||
error ZeroAmount();
|
||||
error InsufficientBalance(address account, uint256 available, uint256 requested);
|
||||
error UnexpectedAssetDelta(uint256 expected, uint256 actual);
|
||||
```
|
||||
|
||||
- [ ] Write failing deposit tests for exact balance/liability/reserve deltas, event emission, zero amount, paused state, inadequate allowance, inadequate wallet balance, and two independent customers. Use six-decimal constants (`1_000e6`) so assertions read like the demo.
|
||||
|
||||
- [ ] Add `FeeOnTransferToken` whose `transferFrom` delivers `99%` of the requested amount. Assert a deposit reverts with `UnexpectedAssetDelta(requested, received)` and that the ERC-20 transfer, bank balance, and liabilities are all rolled back.
|
||||
|
||||
- [ ] Add `ReentrantToken` that attempts a nested `bank.deposit` during `transferFrom`. Assert the nested call receives `ReentrancyGuardReentrantCall` and the outer call either completes once or reverts atomically according to the mock’s configured propagation mode; no double credit is permitted.
|
||||
|
||||
- [ ] Write failing withdrawal tests for exact deltas, event emission, zero amount, paused state, insufficient internal balance (including available/requested values), checks-effects-interactions under a transfer callback, and one account’s withdrawal leaving another account unchanged.
|
||||
|
||||
- [ ] Write the surplus test: deposit 100 mUSDC, transfer 25 mUSDC directly to the proxy, assert reserves `125e6`, liabilities `100e6`, and a normal 100 mUSDC withdrawal succeeds while the 25 mUSDC surplus remains. Assert there is no owner sweep/rescue behavior by keeping such a function out of the interface.
|
||||
|
||||
- [ ] Run red tests:
|
||||
|
||||
```bash
|
||||
npm_config_offline=true forge test --match-path test/BankV1.t.sol -vvv --force
|
||||
```
|
||||
|
||||
Expected red: `deposit`/`withdraw` and their errors/events are absent.
|
||||
|
||||
- [ ] Implement `deposit` with exact received-amount validation and state credit only after a successful transfer:
|
||||
|
||||
```solidity
|
||||
function deposit(uint256 amount) external whenNotPaused nonReentrant {
|
||||
if (amount == 0) revert ZeroAmount();
|
||||
|
||||
uint256 reservesBefore = _asset.balanceOf(address(this));
|
||||
_asset.safeTransferFrom(msg.sender, address(this), amount);
|
||||
uint256 reservesAfter = _asset.balanceOf(address(this));
|
||||
uint256 received = reservesAfter >= reservesBefore ? reservesAfter - reservesBefore : 0;
|
||||
if (received != amount) revert UnexpectedAssetDelta(amount, received);
|
||||
|
||||
_balances[msg.sender] += amount;
|
||||
_totalLiabilities += amount;
|
||||
emit Deposited(msg.sender, amount);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Implement `withdraw` with checks-effects-interactions:
|
||||
|
||||
```solidity
|
||||
function withdraw(uint256 amount) external whenNotPaused nonReentrant {
|
||||
if (amount == 0) revert ZeroAmount();
|
||||
uint256 available = _balances[msg.sender];
|
||||
if (amount > available) revert InsufficientBalance(msg.sender, available, amount);
|
||||
|
||||
_balances[msg.sender] = available - amount;
|
||||
_totalLiabilities -= amount;
|
||||
_asset.safeTransfer(msg.sender, amount);
|
||||
emit Withdrawn(msg.sender, amount);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Add fuzz tests in `BankV1.t.sol`: bound deposit to `[1, 1_000_000e6]`; bound withdrawal to `[1, deposited]`; prove exact reserve/liability/customer deltas and that over-withdraw always reverts. Keep a fixed seed in `foundry.toml` for presentation reproducibility while printing Foundry’s replay seed on failure.
|
||||
|
||||
- [ ] Run focused and aggregate green tests:
|
||||
|
||||
```bash
|
||||
forge fmt
|
||||
npm_config_offline=true forge test --match-path test/BankV1.t.sol -vvv --force
|
||||
npm_config_offline=true forge test --match-path 'test/BankV1*.t.sol' --force
|
||||
```
|
||||
|
||||
Expected: all V1 tests pass with validated proxy deployment enabled.
|
||||
|
||||
- [ ] Commit:
|
||||
|
||||
```bash
|
||||
git add src/BankV1.sol test/BankV1.t.sol test/mocks/FeeOnTransferToken.sol test/mocks/ReentrantToken.sol
|
||||
git commit -m "feat: add V1 custody accounting"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user