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

135 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Task 6: Deploy, seed, inspect, and export deterministic V1 state
**Files:**
- Create: `script/lib/DemoScript.sol`
- Create: `script/DeployV1.s.sol`
- Create: `script/SeedV1Demo.s.sol`
- Create: `script/CheckState.s.sol`
- Create: `test/ScriptPreflight.t.sol`
- Create: `tools/finalize-manifest.mjs`
- Create: `tools/select-manifest.mjs`
- Create: `tools/test-finalize-manifest.mjs`
- Create: `deployments/.gitkeep`
- Modify: `Makefile`
**Interfaces:**
- Consumes an RPC, chain ID, explicit broadcaster, pending manifest, and Foundry broadcast receipts.
- Produces an active schema-versioned public manifest only after receipt/code/postcondition checks, then the exact Act 1 state.
- Establishes local actor indexes: owner `0`, Alice `1`, Bob `2` from Anvils standard development mnemonic.
- [ ] Write `ScriptPreflight.t.sol` around a small public harness for `DemoScript` and cover:
- chain IDs `31337` and `84532` accepted;
- chain IDs `1`, `8453`, and an arbitrary value rejected before broadcast;
- missing/invalid manifest, wrong manifest chain, zero address, and address-without-code rejected;
- internal pending manifests may use deployment block `0`, while active manifests may not;
- local keys derivable only when chain ID is `31337`;
- serialized JSON contains public addresses but never the mnemonic, a private key, `PRIVATE_KEY`, or `MNEMONIC`.
- [ ] Run red:
```bash
forge test --match-path test/ScriptPreflight.t.sol -vvv --force
```
Expected red: `DemoScript` does not exist.
- [ ] Implement `DemoScript` constants and guards:
```solidity
uint256 internal constant ANVIL_CHAIN_ID = 31337;
uint256 internal constant BASE_SEPOLIA_CHAIN_ID = 84532;
string internal constant ANVIL_TEST_PHRASE =
"test test test test test test test test test test test junk";
string internal constant PENDING_MANIFEST_PATH = "deployments/pending.json";
string internal constant ACTIVE_MANIFEST_PATH = "deployments/active.json";
error UnsupportedChain(uint256 chainId);
error ManifestChainMismatch(uint256 expected, uint256 actual);
error MissingCode(string label, address target);
```
Provide narrow helpers for chain checks, `vm.deriveKey` on local only, manifest read/write through `vm.parseJson*`/`vm.serialize*`, code checks, and exact-state assertions. Resolve the input/output manifest from `DEPLOYMENT_MANIFEST_PATH` with a narrow default appropriate to each script. Never log or serialize a derived private key.
- [ ] Write `DeployV1.s.sol` red tests/behavior first, then implement:
1. validate chain and resolve the public `SCRIPT_SENDER` value;
2. start broadcast;
3. deploy `MockUSDC(sender)`;
4. call `Upgrades.deployUUPSProxy("BankV1.sol:BankV1", abi.encodeCall(...))`;
5. stop broadcast;
6. assert code, owner, asset, version `1`, and implementation identity;
7. write `deployments/pending.json` with schema `1`, network, chain, deployment block `0`, display RPC/explorer metadata, token, proxy, implementation, owner, and local actor labels/addresses.
On local, derive account `0`, require it equals `SCRIPT_SENDER`, and broadcast with that derived development key. On Base Sepolia, require `SCRIPT_SENDER` equals the expected owner and use the signer selected by Forges matching `--account`/`--sender` options; never read a raw signing secret from environment.
- [ ] Test `finalize-manifest.mjs` and `select-manifest.mjs` with temporary pending/broadcast fixtures and an injected fake JSON-RPC function. Cover: a pre-broadcast deploy guard that rejects an existing target-chain canonical file, exact proxy transaction match, successful receipt, receipt-derived block, actual chain-ID match, code at token/proxy/implementation, implementation-slot match, failed/missing/ambiguous receipt, partial broadcast, wrong chain, missing code, an existing active manifest from either network remaining untouched until selection, secret-bearing content, atomic same-directory rename, and selection of only a valid confirmed chain manifest. Prove a failure never creates/changes a confirmed or active manifest, and selecting Base preserves Anvil byte-for-byte (and vice versa).
- [ ] Implement the finalizer using Node standard modules and JSON-RPC `fetch`. Its network-free `preflight-deploy <network>` fails before Forge runs when the target canonical file exists and prints the exact safe recovery command (`make reset-local` or `make archive-base-manifest`). Deploy finalization reads `broadcast/DeployV1.s.sol/<chainId>/run-latest.json`, matches the transaction whose created address is the pending proxy, matches its transaction hash to a successful receipt, uses that receipts real block number, queries `eth_chainId`, `eth_getCode` for all three contracts, and queries the EIP-1967 implementation slot. Atomically write the chains canonical `anvil.json` or `base-sepolia.json` only after all checks pass. `select-manifest.mjs` validates a named canonical file and atomically copies it to `active.json`; it never deletes or overwrites the other chain. Never copy a credential-bearing terminal RPC into JSON.
- [ ] Write and implement `SeedV1Demo.s.sol`, hard-guarded to chain `31337`. It must perform and assert this exact sequence:
```text
owner mints Alice 2,000 mUSDC
owner mints Bob 1,000 mUSDC
Alice approves and deposits 1,000 mUSDC
Bob approves and deposits 500 mUSDC
Alice withdraws 100 mUSDC
```
Postconditions: Alice internal `900e6`, Bob internal `500e6`, liabilities `1_400e6`, reserves `1_400e6`, version `1`, no surplus.
- [ ] Write and implement `CheckState.s.sol`. Always print network, block, token, proxy, implementation, owner, pause state, version, each configured actor balance, reserves, liabilities, and surplus. Always fail on `reserves < liabilities`, implementation/manifest mismatch, or manifest chain mismatch. `DEMO_EXPECTED_STAGE=deployed` asserts version `1` and empty accounting; `v1` asserts exact Act 1 values; `invariants` checks network-independent invariants only. Task 11 adds stage `v2`.
- [ ] Add direct Make targets that do not start background processes yet:
```make
RPC_LOCAL := http://127.0.0.1:8545
ANVIL_OWNER := 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
.PHONY: deploy-v1 seed-v1 check-state
deploy-v1:
@node tools/finalize-manifest.mjs preflight-deploy anvil
@SCRIPT_SENDER=$(ANVIL_OWNER) DEPLOYMENT_MANIFEST_PATH=deployments/pending.json npm_config_offline=true forge script script/DeployV1.s.sol:DeployV1 --rpc-url $(RPC_LOCAL) --sender $(ANVIL_OWNER) --broadcast --force
@DEPLOYMENT_MANIFEST_PATH=deployments/pending.json DEMO_EXPECTED_STAGE=deployed forge script script/CheckState.s.sol:CheckState --rpc-url $(RPC_LOCAL) --force
@node tools/finalize-manifest.mjs deploy --rpc-url $(RPC_LOCAL)
@node tools/select-manifest.mjs anvil
seed-v1:
@forge script script/SeedV1Demo.s.sol:SeedV1Demo --rpc-url $(RPC_LOCAL) --broadcast --force
check-state:
@DEMO_EXPECTED_STAGE=$${DEMO_EXPECTED_STAGE:-v1} forge script script/CheckState.s.sol:CheckState --rpc-url $(RPC_LOCAL) --force
```
- [ ] Run unit/preflight green checks:
```bash
forge fmt
forge test --match-path test/ScriptPreflight.t.sol -vvv --force
```
- [ ] Run the first real local smoke test in two terminals. Terminal A:
```bash
anvil --host 127.0.0.1 --port 8545 --chain-id 31337
```
Terminal B:
```bash
make deploy-v1
make seed-v1
DEMO_EXPECTED_STAGE=v1 make check-state
```
Expected: the exact Act 1 table prints and all commands exit `0`. Inspect identical `deployments/anvil.json` and selected `deployments/active.json`, verify their deployment block matches the confirmed proxy receipt, and verify they contain no secret material. Simulate a failed receipt fixture and confirm the finalizer changes neither confirmed nor active files.
- [ ] Commit:
```bash
git add script test/ScriptPreflight.t.sol tools/finalize-manifest.mjs tools/select-manifest.mjs tools/test-finalize-manifest.mjs deployments/.gitkeep Makefile
git commit -m "feat: script deterministic V1 demo state"
```
---