docs: finish UUPS bank demo guide
This commit is contained in:
+91
-28
@@ -2,43 +2,56 @@
|
||||
|
||||
> Educational demo — mock token — never use real funds.
|
||||
|
||||
## Preflight and rehearsal
|
||||
## Exact preflight
|
||||
|
||||
Rehearse once from a clean disposable branch created at the `demo-start` tag. Allow 25 minutes: 3 minutes for preflight, 5 for Act 1, 10 for the Codex change and verification, 4 for Act 3, and 3 for questions. Keep two terminals visible and open the browser only after Vite reports ready.
|
||||
|
||||
Before the audience arrives:
|
||||
Rehearse from a clean disposable branch or worktree created at the immutable `demo-start` tag. Use two terminals, keep the browser closed until Vite reports ready, and run this before the audience arrives:
|
||||
|
||||
```bash
|
||||
make setup
|
||||
make reset-local
|
||||
make setup
|
||||
make doctor
|
||||
make verify
|
||||
git diff --check
|
||||
git status --short
|
||||
```
|
||||
|
||||
The doctor must show Foundry `1.7.1`, Node `24.18.0`, npm `11.17.0`, initialized dependencies, writable runtime paths, and free ports `8545`/`5173`. Verification must exit `0`. No upgrade or transfer command runs until `make verify` passes.
|
||||
The doctor must report Foundry `1.7.1`, Node `24.18.0`, npm `11.17.0`, initialized dependencies, writable runtime paths, and free ports `8545` and `5173`. `make verify` and `git diff --check` must exit `0`. At `demo-start`, tracked status must be clean. Do not upgrade after a partial or failed gate.
|
||||
|
||||
## The three-act story
|
||||
## Ten-minute rehearsal
|
||||
|
||||
Use this exact local timing once the dependencies are installed:
|
||||
|
||||
| Time | Action | Evidence to say aloud |
|
||||
|---|---|---|
|
||||
| `0:00–1:00` | Run the preflight checks and start `make demo-local` in terminal 1. | Anvil is chain `31337`; Vite and Anvil are project-owned and attached. |
|
||||
| `1:00–3:00` | Run the V1 state check in terminal 2 and open the console. | Stable proxy, distinct implementation, version `1`, Alice `900`, Bob `500`, reserves = liabilities = `1,400 mUSDC`. |
|
||||
| `3:00–5:30` | Deliver the live Codex prompt and review the prepared Act 2 diff. | V2 inherits V1, adds no storage or initializer, and the browser remains read-only. |
|
||||
| `5:30–7:00` | Point to the completed `make verify` evidence, then run `make upgrade-v2`. | All gates passed before broadcast; proxy and state remain stable while implementation and version change. |
|
||||
| `7:00–9:00` | Run `make demo-transfer`, the V2 check, and the HTTP check. | Alice `650`, Bob `750`, reserves and liabilities still `1,400`; one decoded internal transfer and no token transfer. |
|
||||
| `9:00–10:00` | Read the trust disclosure, Ctrl-C terminal 1, and run `make reset-local`. | The owner remains central; local cleanup targets only validated project processes and artifacts. |
|
||||
|
||||
If a live Codex edit takes longer, treat this as the prepared reference rehearsal and allow a separate coding block. The verification-before-upgrade rule never changes to meet the clock.
|
||||
|
||||
## Two-terminal three-act sequence
|
||||
|
||||
### Act 1 — Prepared V1 establishes trust
|
||||
|
||||
First terminal:
|
||||
Terminal 1 remains attached:
|
||||
|
||||
```bash
|
||||
make demo-local
|
||||
```
|
||||
|
||||
Second terminal:
|
||||
Terminal 2:
|
||||
|
||||
```bash
|
||||
DEMO_EXPECTED_STAGE=v1 make check-state
|
||||
curl --fail http://127.0.0.1:5173/
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:5173/`; Anvil is at `http://127.0.0.1:8545`. Call out that every browser read targets the proxy, while the distinct implementation address is shown only to teach delegation. The exact expected state is Alice `900 mUSDC`, Bob `500 mUSDC`, liabilities `1,400 mUSDC`, reserves `1,400 mUSDC`, surplus `0`, and version `1`. Point to deposit, withdrawal, and upgrade/ownership events, then state that the browser is read-only.
|
||||
Open `http://127.0.0.1:5173/`; Anvil is at `http://127.0.0.1:8545`. Call out that every application read and write targets the proxy. The implementation address is separate and displayed only to explain delegation. The expected state is Alice `900 mUSDC`, Bob `500 mUSDC`, liabilities `1,400 mUSDC`, reserves `1,400 mUSDC`, surplus `0`, pause state `false`, and version `1`.
|
||||
|
||||
Explain the initial flow: scripts minted 2,000 mUSDC to Alice and 1,000 to Bob; Alice deposited 1,000 and withdrew 100; Bob deposited 500. The stable proxy holds reserves and storage. Implementation code runs against that storage with `delegatecall`.
|
||||
The scripts minted `2,000 mUSDC` to Alice and `1,000 mUSDC` to Bob; Alice deposited `1,000` and withdrew `100`; Bob deposited `500`. The stable proxy holds the reserves and application storage. Point to the deposits, withdrawal, initial ownership, and upgrade-to-V1 events, then state that the console is read-only and never signs.
|
||||
|
||||
### Act 2 — Codex changes the system live
|
||||
|
||||
@@ -46,35 +59,85 @@ Give Codex this approved prompt verbatim:
|
||||
|
||||
> Add `BankV2` with customer-to-customer internal transfers. Preserve the UUPS storage layout and all V1 behavior. Add unit, fuzz, invariant, and upgrade-regression tests; an owner upgrade script; a scripted Alice-to-Bob transfer; exported ABI support; and the read-only console updates needed to show V2 and its transfer event. Explain each security decision. Do not perform an upgrade until all verification passes.
|
||||
|
||||
Ask Codex to show the diff and explain storage compatibility, authorization, conservation, error paths, and why the browser remains read-only. The expected prepared change adds transfer-aware source, tests, scripts, ABI output, and console rendering without changing V1 storage. Run:
|
||||
The bounded local Act 2 diff from `demo-start` consists of these files:
|
||||
|
||||
- Contracts and direct tests: `src/BankV2.sol`, the initializer-visibility compatibility edit in `src/BankV1.sol`, `test/BankV2.t.sol`, `test/BankUpgrade.t.sol`, `test/mocks/IncompatibleBank.sol`, and `test/mocks/NonUUPSImplementation.sol`.
|
||||
- Upgrade/demo/invariant path: `Makefile`, `script/CheckState.s.sol`, `script/UpgradeV2.s.sol`, `script/TransferV2Demo.s.sol`, `test/BankInvariant.t.sol`, `test/ScriptPreflight.t.sol`, and `test/helpers/BankV2Handler.sol`.
|
||||
- Manifest and ABI bridge: `tools/finalize-manifest.mjs`, `tools/sync-web-artifacts.mjs`, `tools/test-finalize-manifest.mjs`, and `tools/test-sync-web-artifacts.mjs`.
|
||||
- Read-only console: `web/src/App.test.tsx`, `web/src/components/ActivityTimeline.tsx`, `web/src/data/bankClient.ts`, `web/src/data/bankClient.test.ts`, and `web/src/types/dashboard.ts`.
|
||||
|
||||
The V1 compatibility edit changes `initialize` visibility from `external` to `public` so the pinned upgrades-core tooling recognizes the initializer as inherited by `BankV2`; it does not change the external ABI, initializer guard, or stored state. Generated ABI and deployment files may appear during execution but are reproducible runtime artifacts, not live source edits. The optional Base Sepolia files are a later encore and are not required for the local Act 2 story.
|
||||
|
||||
Ask Codex to show the diff and explain layout preservation, owner authorization, transfer conservation, error paths, snapshot postconditions, and why the browser remains read-only. Then run:
|
||||
|
||||
```bash
|
||||
make verify
|
||||
```
|
||||
|
||||
If any gate fails, stop and let Codex diagnose it. Do not run an upgrade merely because a partial test command passed.
|
||||
Point to each successful layer in the output: `forge fmt --check`; clean forced build; generated ABI freshness; the pinned OpenZeppelin upgrades CLI; 102 Solidity unit, fuzz, invariant, upgrade-regression, and script tests; 16 manifest-finalizer tests; 26 process-safety cases; offline Base-config and project-scanner gates; then web lint, TypeScript checking, 55 Vitest tests across six files, and the production Vite build. In the upgrade tests, highlight the validator accepting `BankV1` → `BankV2`, rejecting an incompatible layout, rejecting non-UUPS logic, and preserving the snapshot. If any layer fails, do not upgrade.
|
||||
|
||||
### Act 3 — V2 proves continuity
|
||||
|
||||
After Codex has implemented these targets and the full gate has passed:
|
||||
Continue in terminal 2 only after the complete gate passes:
|
||||
|
||||
```bash
|
||||
make upgrade-v2
|
||||
make demo-transfer
|
||||
make check-state
|
||||
DEMO_EXPECTED_STAGE=v2 make check-state
|
||||
curl --fail http://127.0.0.1:5173/
|
||||
```
|
||||
|
||||
The expected result is the same proxy and a new implementation, version `2`, Alice `650 mUSDC`, Bob `750 mUSDC`, liabilities `1,400 mUSDC`, reserves `1,400 mUSDC`, and a decoded 250 mUSDC internal transfer event. No ERC-20 transfer should accompany the ledger transfer. Refresh only if the console has not observed the next block; otherwise let its live status prove the change.
|
||||
At the upgrade boundary, compare the printed identities and state:
|
||||
|
||||
- the proxy address is exactly the Act 1 proxy;
|
||||
- the implementation address is different;
|
||||
- owner, asset, pause state, Alice/Bob addresses and balances, liabilities, reserves, surplus, and deployment block are unchanged;
|
||||
- version changes from `1` to `2`; and
|
||||
- the upgrade finalizer matches the `Upgraded` event and live ERC-1967 implementation slot before publishing the V2 manifest.
|
||||
|
||||
After the transfer, call out Alice `650 mUSDC`, Bob `750 mUSDC`, liabilities `1,400 mUSDC`, reserves `1,400 mUSDC`, surplus `0`, and version `2`. The timeline must decode `BalanceTransferred(Alice, Bob, 250e6)` as “Alice transferred 250.000000 mUSDC to Bob.” That transaction has no MockUSDC `Transfer` log, and the unchanged reserves prove no ERC-20 left or entered the proxy. Refresh only if the console has not observed the new block; normally its live synchronization should show the change.
|
||||
|
||||
## Recovery without broad cleanup
|
||||
|
||||
- **Occupied port:** `make demo-local` refuses to claim either port. Use `curl http://127.0.0.1:8545` and `curl http://127.0.0.1:5173/` plus your operating system's process inspection to identify the external owner. Stop it yourself only after proving ownership; the project never uses broad process matching.
|
||||
- **Failed edit or test:** do not upgrade. Keep the failing command and diff visible, let Codex diagnose on the disposable live branch, or finish with the verified V1. If time expires, create a new disposable branch/worktree from `demo-start`; never reset or overwrite unrelated work.
|
||||
- **Occupied port:** `make demo-local` refuses to claim either port. Probe with `curl http://127.0.0.1:8545` and `curl http://127.0.0.1:5173/`, then use operating-system process inspection to identify the external owner. Stop it only after proving ownership. The project never uses broad process matching.
|
||||
- **Recorded stale process:** run `make reset-local`. It validates numeric PID, process group, command signature, and Linux start tick before signaling. A mismatched live process is preserved and reset exits nonzero.
|
||||
- **Stale console:** confirm `DEMO_EXPECTED_STAGE=v1 make check-state`, inspect `.demo/vite.log`, and use `curl --fail http://127.0.0.1:5173/`. The console labels stale/disconnected state and preserves the last good snapshot rather than inventing zeros.
|
||||
- **Failed test or live edit:** do not upgrade. Save the diff for review, continue diagnosis on the disposable live branch, or start a new disposable branch from `demo-start`; never overwrite unrelated work. The prepared V1 remains the successful ending if time expires.
|
||||
- **Unexpected child exit:** the attached launcher stops its other validated group. Inspect `.demo/anvil.log` and `.demo/vite.log`, then run `make reset-local` and restart.
|
||||
- **Stale UI:** run the stage-appropriate check (`DEMO_EXPECTED_STAGE=v1 make check-state` before transfer or `DEMO_EXPECTED_STAGE=v2 make check-state` after it), inspect `.demo/vite.log`, and use `curl --fail http://127.0.0.1:5173/`. A stale/disconnected console preserves the last good snapshot and labels it; it never invents zeros.
|
||||
- **Partial local manifest:** never copy `pending.json` or `upgrade-pending.json` into a confirmed manifest and never hand-edit `active.json`. If an upgrade broadcast succeeded but a transient finalizer step failed, preserve the staging and broadcast files, diagnose the cause, and rerun `node tools/finalize-manifest.mjs upgrade --rpc-url http://127.0.0.1:8545`; then run `make sync-artifacts`. Otherwise stop the attached process, run `make reset-local`, and restart from a fresh V1. Confirmed manifests remain unchanged on a failed finalization.
|
||||
- **Unexpected child exit:** the attached launcher stops its other validated group. Inspect `.demo/anvil.log` and `.demo/vite.log`, run `make reset-local`, and restart.
|
||||
- **Unavailable Base service:** stop the optional encore, record whether the faucet, RPC, or explorer failed, and leave the completed local result untouched. Never substitute another chain or expose a signing secret to rescue an encore.
|
||||
|
||||
End the local session with Ctrl-C in the attached terminal, then `make reset-local`. The optional public encore is outside this prepared V1 run: local success does not depend on Base Sepolia, a faucet, an explorer, a wallet, or any external RPC.
|
||||
End the local session with Ctrl-C in terminal 1, then run:
|
||||
|
||||
```bash
|
||||
make reset-local
|
||||
bash tools/test-process-safety.sh
|
||||
bash tools/test-base-config.sh
|
||||
git diff --check
|
||||
git status --short
|
||||
```
|
||||
|
||||
### Recover the verified reference without touching current work
|
||||
|
||||
`demo-complete` is a reference/recovery checkpoint, not a reason to reset the current checkout. From the repository root, first verify the tag and inspect current work:
|
||||
|
||||
```bash
|
||||
git cat-file -t demo-complete
|
||||
git tag -n99 demo-complete
|
||||
git rev-parse 'demo-complete^{}'
|
||||
git status --short
|
||||
```
|
||||
|
||||
The type must be `tag`, the annotation must read `Verified reference solution for UUPS bank demo`, and the peeled target is the verified documentation commit. This checks the required annotated tag without implying that it carries a GPG signature.
|
||||
|
||||
Then create a new branch in a new worktree; choose unused names if either example name already exists:
|
||||
|
||||
```bash
|
||||
git worktree add -b recovery/uups-bank-demo .worktrees/uups-bank-demo-recovery demo-complete
|
||||
git -C .worktrees/uups-bank-demo-recovery status --short
|
||||
```
|
||||
|
||||
This peels the annotated tag into a new branch without switching, resetting, cleaning, stashing, or overwriting the current worktree. Never delete an existing recovery directory or move either demo tag as part of recovery.
|
||||
|
||||
## Optional Base Sepolia encore
|
||||
|
||||
@@ -85,9 +148,9 @@ cast wallet import uups-bank-base --interactive
|
||||
cast wallet address --account uups-bank-base
|
||||
```
|
||||
|
||||
Copy the displayed public address to `BASE_SEPOLIA_SENDER`, set `BASE_SEPOLIA_ACCOUNT=uups-bank-base`, and verify the two refer to the same account. Choose a different nonzero public address for `BASE_SEPOLIA_RECIPIENT`. Fund only the displayed Base Sepolia sender address with Base Sepolia test ETH. Never paste the key or password into Codex, shell history, or `.env`; Foundry requests the encrypted-keystore password through its interactive prompt.
|
||||
Copy the displayed public address to `BASE_SEPOLIA_SENDER`, set `BASE_SEPOLIA_ACCOUNT=uups-bank-base`, and choose a different nonzero public address for `BASE_SEPOLIA_RECIPIENT`. The Base manifest schema is exactly two nested actors in order: `Presenter` at the configured sender, then `Recipient` at the configured recipient. Fund only the displayed Base Sepolia sender with Base Sepolia test ETH. Never paste the key or password into Codex, shell history, or `.env`; Foundry requests the encrypted-keystore password interactively.
|
||||
|
||||
Copy `.env.example` to `.env` and configure:
|
||||
Copy `.env.example` to `.env` and configure only public identifiers and endpoints:
|
||||
|
||||
```dotenv
|
||||
# Terminal RPC may be credentialed; never copied into browser artifacts.
|
||||
@@ -107,7 +170,7 @@ bash tools/test-base-config.sh
|
||||
make -n deploy-base-sepolia BASE_SEPOLIA_RPC_URL=https://terminal.invalid BASE_SEPOLIA_PUBLIC_RPC_URL=https://public.invalid BASE_SEPOLIA_ACCOUNT=demo BASE_SEPOLIA_SENDER=0x1111111111111111111111111111111111111111 BASE_SEPOLIA_RECIPIENT=0x2222222222222222222222222222222222222222
|
||||
```
|
||||
|
||||
The dry run must contain `--account`, `--sender`, and `--slow`, and must not contain a raw-key option. The URLs above are deliberately fake; these checks require no keystore, faucet, or network. Then, only with explicit authorization and Base Sepolia test funds, execute:
|
||||
The dry run must contain `--account`, `--sender`, and `--slow`, and no raw-key option. The URLs are deliberately fake; these checks need no keystore, faucet, or network. Only with explicit authorization and Base Sepolia test funds may the presenter run:
|
||||
|
||||
```bash
|
||||
make deploy-base-sepolia
|
||||
@@ -115,11 +178,11 @@ make upgrade-base-sepolia
|
||||
make transfer-base-sepolia
|
||||
```
|
||||
|
||||
Deployment mints valueless mUSDC only to Presenter and deposits `1,000 mUSDC`; Recipient starts at `0`. The V2 transfer produces Presenter `750 mUSDC` and Recipient `250 mUSDC`, while reserves and liabilities stay `1,000 mUSDC`. Each command runs an invariant-only state check. The confirmed browser manifest contains only the intentionally public browser RPC plus `https://sepolia.basescan.org`; the terminal RPC is never serialized.
|
||||
Deployment mints valueless mUSDC only to Presenter and deposits `1,000 mUSDC`; Recipient starts at `0`. The V2 internal transfer produces Presenter `750 mUSDC` and Recipient `250 mUSDC`, while reserves and liabilities remain `1,000 mUSDC`. Each command runs an invariant-only state check. The confirmed browser manifest contains only the intentionally public browser RPC and `https://sepolia.basescan.org`; the terminal RPC is never serialized.
|
||||
|
||||
Before an intentional Base redeployment, run `make archive-base-manifest`. It moves only `deployments/base-sepolia.json` to a validated timestamped sibling and leaves `active.json` and Anvil state untouched. A subsequent finalizer may create a new Base canonical manifest; the old active copy remains the browser fallback until `make select-base-sepolia` succeeds. Use `make select-anvil` to return the browser to the local manifest. If a faucet, RPC, or explorer fails, record the optional failure and stop—the completed local demo remains the successful outcome.
|
||||
Before an intentional Base redeployment, run `make archive-base-manifest`. It moves only `deployments/base-sepolia.json` to a validated timestamped sibling and leaves `active.json` and Anvil state untouched. A subsequent finalizer may create a new Base canonical manifest; the old active copy remains the browser fallback until `make select-base-sepolia` succeeds. Use `make select-anvil` to return the browser to the local manifest.
|
||||
|
||||
## Closing trust disclosure checklist
|
||||
## Closing trust disclosure and tool boundaries
|
||||
|
||||
Read these points while the matching console panel is visible:
|
||||
|
||||
@@ -129,4 +192,4 @@ Read these points while the matching console panel is visible:
|
||||
- UUPS mistakes can corrupt state or permanently brick upgradeability.
|
||||
- A real custody product requires professional audits, operational key controls, multisig or timelocked governance, incident procedures, legal advice, and jurisdiction-specific compliance work.
|
||||
|
||||
Codex supplies the cross-stack implementation, tests, orchestration, and explanation. OpenZeppelin supplies reviewed contract primitives and upgrade validation; Foundry supplies compilation, tests, scripts, and the local chain. None of those tools turns this teaching artifact into an audited or regulated custody product.
|
||||
Codex performs the repository-specific work: application and UI implementation, tests, scripts, lifecycle orchestration, manifest handling, and explanation. OpenZeppelin supplies reviewed reusable primitives and a validator that checks declared upgrade/storage compatibility; it does not audit this application's economics, owner choices, or operations. Foundry compiles and executes the declared tests and scripts and provides local Anvil; it guarantees neither test completeness nor production safety. None of Codex, OpenZeppelin, Foundry, or a passing gate turns this teaching artifact into audited, decentralized, or regulated custody software.
|
||||
|
||||
Reference in New Issue
Block a user