0007. LatticeRegistry is a standalone two-tier registry
- Status: Proposed. The design is deployed in development and tested, but #176 (open) still compares it with alternatives before any canonical release relies on it. This record becomes Accepted, or is superseded, when #176 settles.
- Date: built for #118 (2026-07-08); hardening evidence from #315 (2026-10-09); recorded 2026-10-09
Context
Every Lattice diamond can reuse one deployed copy of each facet instead of deploying its own. Something on chain has to say which address holds the canonical code for a facet, which selectors to cut for it, and that neither has changed. Everything that deploys through it trusts it, so its trust surface should be as small as possible. #176 records that being non-upgradeable does not make the current design stable: it may still change before the first major release, and the earlier "deploy-once" wording in #118 no longer describes a stability promise.
Options considered
#176 asks for this comparison; it is not finished.
- Two tiers (current). A permissionless codehash index plus an owner-curated catalog.
- A curated catalog only. Drops the permissionless index.
- Pinned release manifests and no on-chain catalog. Deployers pass custom cuts; the chain verifies nothing.
Decision (proposed)
LatticeRegistry is a standalone, non-upgradeable contract: no
diamond, no ERC-7201 storage, no proxy.
- Tier A, permissionless.
attest(deployed)records the first address seen with a runtime codehash andresolve(codehash)returns it. No admin. - Tier B, curated.
registerandsetLatestare owner-only under a two-step ownership transfer (a multisig from the first mainnet release). A(nameHash, version)record is immutable once written.latest(nameHash)is a movable convenience pointer; security-critical consumers pin an exact version. A curated facet must implement ERC-8153exportSelectors(); registration pins its codehash and the hash of its selector blob, andgetCutre-checks both on every read.
Consequences
- A curated record cannot be repointed, so a pinned version always yields the same cut or a drift revert.
- Tier A identifies code, not behaviour. Two instances with one codehash can export different selectors
when the exporter reads its own storage, and
resolvereturns the first attester (finding R-3 in the threat model). TheILatticeRegistryNatSpec lists the assumptions under which same-codehash addresses are equivalent and saysresolveis not a selector source (#321). - Registration reads exports through a
staticcall, which cannot enforcepure, and a hostile exporter could make registration or reads expensive or malformed. Since #321 every exporter call gets at most 100,000 gas and 8,192 bytes of return, and a malformed return isNotERC8153atregisterandSelectorDrifton a read. - No production script resolves registry entries today:
BaseDeploy._assemblepasses custom cuts, andDeployReleaseonly writes the registry.
Confirmation
LatticeRegistryTest,LatticeRegistryFuzz,LatticeRegistryHostileExporterTestand the registry invariant suite, mapped to each invariant in the threat model.
References
ILatticeRegistry.sol(the trust model as shipped)- Registry and factory threat model
- REGISTRY_DEPLOYMENTS.md
- #118, #176, #315