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