Files
uupl-smart-contract/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-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

3.7 KiB

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:

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:

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:
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:
/// @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:

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:
git add src/BankV1.sol test/helpers/BankTestBase.sol test/BankV1Admin.t.sol
git commit -m "feat: establish UUPS bank V1"