### Task 3: Establish the V1 proxy, initialization, views, and administration **Files:** - Create: `src/BankV1.sol` - Create: `test/helpers/BankTestBase.sol` - Create: `test/BankV1Admin.t.sol` **Interfaces:** - Produces `initialize(address,address)`, `asset()`, `balanceOf(address)`, `totalLiabilities()`, `contractVersion()`, `pause()`, `unpause()`, and owner-authorized UUPS upgrades. - Consumes `MockUSDC` and OpenZeppelin `Upgrades.deployUUPSProxy`. - Freezes V1 application storage as `_asset`, `_balances`, `_totalLiabilities`, then `uint256[47] __gap`. - [ ] Create `BankTestBase.sol` with deterministic `owner`, `alice`, `bob`, and `stranger` addresses from `makeAddr`; deploy `MockUSDC`; deploy the proxy with: ```solidity proxy = Upgrades.deployUUPSProxy( "BankV1.sol:BankV1", abi.encodeCall(BankV1.initialize, (address(token), owner)) ); bank = BankV1(proxy); ``` All bank calls in tests target `proxy`; retain `implementation = Upgrades.getImplementationAddress(proxy)` only for assertions. - [ ] Write failing admin tests for: - initialized asset, owner, unpaused state, zero liabilities, and version `1`; - initialization with zero asset; - initialization with zero owner through `OwnableInvalidOwner`; - proxy double initialization; - direct implementation initialization; - only owner can pause/unpause; - views still work while paused; - `_authorizeUpgrade` rejects a non-owner through `upgradeToAndCall`; - implementation `proxiableUUID()` is available directly while the proxy-context call reverts. - [ ] Run the focused suite and observe red: ```bash npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force ``` Expected red: `BankV1` is missing. - [ ] Implement the minimum V1 skeleton with these imports and inheritance order: ```solidity import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; contract BankV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuard { IERC20 internal _asset; mapping(address account => uint256 balance) internal _balances; uint256 internal _totalLiabilities; uint256[47] private __gap; } ``` Use `error InvalidAsset(address asset);`. The initializer checks the asset before calling only `__Ownable_init(initialOwner)` and `__Pausable_init()`. `Initializable`, `UUPSUpgradeable`, and `ReentrancyGuard` are stateless/shared in pinned OpenZeppelin 5.6.1 and have no initializer calls. - [ ] Add the only permitted validator annotation and no other bypass: ```solidity /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } ``` - [ ] Add `pause`/`unpause` with `onlyOwner`, simple views, `contractVersion() public pure virtual returns (uint256)`, and `_authorizeUpgrade(address) internal override onlyOwner {}`. Do not add deposits or withdrawals yet. - [ ] Run green verification and inspect storage: ```bash forge fmt npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force forge inspect BankV1 storage-layout ``` Expected: tests pass and the application fields appear in the frozen order. OpenZeppelin namespaced/stateless internals must not be mistaken for permission to reorder application fields. - [ ] Commit: ```bash git add src/BankV1.sol test/helpers/BankTestBase.sol test/BankV1Admin.t.sol git commit -m "feat: establish UUPS bank V1" ``` ---