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

7.8 KiB
Raw Blame History

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:

forge test --match-path test/ScriptPreflight.t.sol -vvv --force

Expected red: DemoScript does not exist.

  • Implement DemoScript constants and guards:
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:

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:

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:
forge fmt
forge test --match-path test/ScriptPreflight.t.sol -vvv --force
  • Run the first real local smoke test in two terminals. Terminal A:
anvil --host 127.0.0.1 --port 8545 --chain-id 31337

Terminal B:

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:
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"