Skip to content

lists(deploy): pre-mainnet schema-UID pin + CREATE2 reconciliation (PR #20 F1/F2/F5) #21

@JamesCarnley

Description

@JamesCarnley

Follow-up from external review of #20 (review URL). Must land before the mainnet freeze ADR closes. Devnet-shipping is unaffected.

ADR-0044 §8 (Accepted, immutable) mandates three things for the ListEntryResolver deploy that the current implementation does not provide:

  1. CREATE2 with deterministic salt for the resolver address.
  2. Deploy-time assert(LIST_ENTRY_SCHEMA_UID == expected) against a hardcoded constant pinned to ADR-0046's recorded UID (0xc303f1…5a86e).
  3. CI pin check analogous to ADR-0037's git diff --exit-code packages/nextjs/contracts/deployedContracts.ts.

Current state in packages/hardhat/deploy/09_lists.ts:

  • L62-64 use ethers.getCreateAddress({from: deployer, nonce}) — plain CREATE, not CREATE2.
  • L198-209 only verify the LIST_SCHEMA_UID baked into the resolver's immutable against the one we just computed (internal consistency); there is no comparison against a hardcoded EXPECTED_LIST_ENTRY_SCHEMA_UID.
  • No CI job verifies a fresh deploy reproduces ADR-0046's recorded bytes.

Concrete failure modes if shipped to mainnet unchanged:

  • Nonce drift on persistent networks (stray deployer tx between yarn-deploy runs, parallel worktrees, faucet/manual fund) silently shifts the CREATE address → shifts the LIST_ENTRY schema UID → orphans every prior entry. The internal-consistency assert can't catch this because both sides update.
  • No compile-time anchor to ADR-0046's recorded UID.

Companion finding: ADR-0044 §8 ↔ ADR-0046 ↔ code drift

ADR-0044 §8 says CREATE2. ADR-0046 §"Recorded addresses" describes the resolver as "nonce-deterministic" — implicitly walking back the CREATE2 mandate without an explicit supersession. Per docs/agent-workflow.md, accepted ADRs aren't editable in place. Reconcile via either:

  • (a) Migrate the deploy to true CREATE2 (OZ Create2.deploy or hardhat-deploy deterministicDeployment) — matches ADR-0044 §8 as written.
  • (b) Write a small superseding ADR documenting why CREATE-with-nonce + the partial-deploy guard at L78-84 is sufficient, then add the EXPECTED-UID hardcoded assert + CI pin job.

Either path lands a tamper-detection CI gate before the schema UID becomes Etched.

Sub-item: registry-resolver readback assert (F5)

09_lists.ts:198-209 does not do assert(schemaRegistry.getSchema(listEntrySchemaUID).resolver == listEntryResolver.target). The address-prediction invariant at L84-91 already catches the practical drift case, but ADR-0044 §8 calls for the readback assert specifically. ~5 lines.

Suggested fix sketch

```typescript
// Pinned to ADR-0046 §"Recorded addresses"
const EXPECTED_LIST_SCHEMA_UID = "0x61363a…" as const;
const EXPECTED_LIST_ENTRY_SCHEMA_UID = "0xc303f1…5a86e" as const;

if (listSchemaUID !== EXPECTED_LIST_SCHEMA_UID)
throw new Error(`LIST_SCHEMA_UID drift: expected ${EXPECTED_LIST_SCHEMA_UID}, got ${listSchemaUID}`);
if (listEntrySchemaUID !== EXPECTED_LIST_ENTRY_SCHEMA_UID)
throw new Error(`LIST_ENTRY_SCHEMA_UID drift: expected ${EXPECTED_LIST_ENTRY_SCHEMA_UID}, got ${listEntrySchemaUID}`);

const leRecord = await schemaRegistry.getSchema(listEntrySchemaUID);
if (leRecord.resolver.toLowerCase() !== String(listEntryResolver.target).toLowerCase()) {
throw new Error(`LIST_ENTRY schema points at wrong resolver\nExpected: ${listEntryResolver.target}\nGot: ${leRecord.resolver}`);
}
```

Plus a `lists-pin-check` CI job mirroring `deploy-pin-check`: fresh deploy against the pinned fork → `git diff --exit-code` on a checked-in `packages/hardhat/lists-pin.json` with the two UIDs and two addresses.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions