### 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 Anvil’s 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 Forge’s 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 ` 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//run-latest.json`, matches the transaction whose created address is the pending proxy, matches its transaction hash to a successful receipt, uses that receipt’s real block number, queries `eth_chainId`, `eth_getCode` for all three contracts, and queries the EIP-1967 implementation slot. Atomically write the chain’s 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" ``` ---