Skip to content
Lattice

ILatticeRegistry

Git Source

Title: ILatticeRegistry

Author: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)

Read / admin surface of the LatticeRegistry singleton — the two-tier, immutable, deploy-once address book that lets every Lattice diamond reuse canonical facet bytecode instead of re-CREATEing byte-identical facets on every deployment.

TRUST MODEL (issue #118). The registry is deliberately a standalone, NON-upgradeable plain contract (no diamond, no ERC-7201, no proxy) — the smallest possible trust surface for the thing every deployment depends on. Two tiers:

  • Tier A (permissionless, trustless): attest / resolve. A content-addressed index from a runtime codehash to the FIRST address attested with it. attest is self-verifying (it reads deployed.codehash on-chain — impossible to spoof) and first-write-wins. No curation, no admin. resolve answers ONE question: "where is a contract with exactly this code?". It is a code-identity lookup, NOT a selector source and not a statement about behaviour. Two addresses with one codehash behave the same only when the diamond delegatecalls them AND the facet meets every assumption below; Lattice facets do, arbitrary contracts need not:
  1. no reads of the facet's OWN storage on any path (a direct call or staticcall reads the facet's storage, which can differ between two instances of the same code);
  2. a pure ERC-8153 exportSelectors() (a view exporter can answer differently per instance, and a staticcall cannot enforce pure);
  3. no proxy used as a facet (a proxy's codehash says nothing about the code it forwards to);
  4. selectors come from Tier B (getSelectors / getCut), never from resolve. register mirrors a curated facet into Tier A only when its codehash is unmapped, so a curated record can name a different address than resolve returns for the same codehash.
  • Tier B (curated, append-only): register / setLatest + views. Ownable2Step (→ multisig) governs ONLY this namespace. A (nameHash, version) record is IMMUTABLE once written (invariant I1) — never repointed; latest(nameHash) is a curator-movable convenience pointer that security-critical consumers ignore in favour of pinning an exact version (invariant I3). Curated facets MUST implement ERC-8153 exportSelectors(); register pulls and validates the selectors and pins keccak256(blob), and the live-read views re-verify against that pin so an impure or lying exportSelectors() can never drift the cut a consumer receives (invariant I2 for selectors).

EXPORTER CALLS ARE BOUNDED. Every exportSelectors() read (at register and on each live read) forwards at most 100,000 gas and accepts at most 8,192 bytes of return data; a call that runs out of gas, reverts, or returns more is treated as a non-conformant export. Return data is decoded by hand with exactly the acceptance rule of abi.decode(ret, (bytes)) (any in-bounds offset, trailing bytes ignored), so a malformed encoding is reported as LatticeRegistry__NotERC8153 at registration and as LatticeRegistry__SelectorDrift on a live read, never as an empty revert or a Panic.

version is a semver value packed into a uint64 as major (16 bits) | minor (24) | patch (24) ((major &lt;&lt; 48) | (minor &lt;&lt; 24) | patch), so lexicographic uint64 ordering equals semver ordering. The registry treats it as an OPAQUE, ordered key; it never decodes the fields. Packed version 0 (i.e. 0.0.0) is RESERVED as the "latest unset" sentinel and is not registrable.

Functions

attest

Permissionlessly record that deployed carries a given runtime codehash (first-write-wins).

Self-verifying: reads deployed.codehash on-chain; reverts LatticeRegistry__EmptyCode for an EOA / empty account. Re-attesting an already-mapped codehash is a documented silent no-op — the mapping is immutable once set, so the first attester wins and no event re-fires.

function attest(address deployed) external;

Parameters

NameTypeDescription
deployedaddressThe address whose runtime code should be indexed.

resolve

Resolve a runtime codehash to the first address attested with it (0 if none).

A code-identity lookup only: it never says which selectors an address exports, and two addresses with one codehash are interchangeable only under the Tier A assumptions in the interface NatSpec.

function resolve(bytes32 codehash) external view returns (address deployed);

Parameters

NameTypeDescription
codehashbytes32The runtime codehash to look up.

Returns

NameTypeDescription
deployedaddressThe first attested address, or address(0) if unmapped.

register

Register a new curated facet under (nameHash, version) (owner-only, append-only).

Reverts LatticeRegistry__RecordExists if the record already exists (I1), LatticeRegistry__InvalidVersion for version 0, LatticeRegistry__EmptyCode for a codeless facet, and LatticeRegistry__NotERC8153 if IERC8153(facet).exportSelectors() fails the ERC-8153 contract. Pins keccak256(selectors), mirrors the codehash into Tier A, and emits Registered.

function register(bytes32 nameHash, uint64 version, address facet) external;

Parameters

NameTypeDescription
nameHashbytes32The curated name key.
versionuint64The semver-packed version (must be non-zero).
facetaddressThe canonical facet address (must have code and implement ERC-8153).

setLatest

Point latest(nameHash) at an already-registered version (owner-only).

The target record must exist (LatticeRegistry__RecordNotFound otherwise). Moving the pointer to an OLDER version is allowed — it is only a convenience pointer, never the pinned record (I3).

function setLatest(bytes32 nameHash, uint64 version) external;

Parameters

NameTypeDescription
nameHashbytes32The curated name key.
versionuint64The registered version to flag as latest.

get

Read the exact record for (nameHash, version).

Reverts LatticeRegistry__RecordNotFound if unregistered.

function get(bytes32 nameHash, uint64 version) external view returns (Record memory record);

Parameters

NameTypeDescription
nameHashbytes32
versionuint64

Returns

NameTypeDescription
recordRecord

latest

Read the record currently flagged as latest for a name.

Reverts LatticeRegistry__LatestUnset if setLatest was never called for nameHash.

function latest(bytes32 nameHash) external view returns (Record memory record);

Parameters

NameTypeDescription
nameHashbytes32

Returns

NameTypeDescription
recordRecord

getMany

Batch get: one record per key, in order. A key with version == 0 reads the record latest(nameHash) points at, the way a factory recipe entry with version 0 resolves.

Reverts on the first key that get (or latest, for version 0) would revert on. Records are read from storage only; use getSelectors / getCut for live-verified selectors.

function getMany(RecordKey[] calldata keys) external view returns (Record[] memory records);

Parameters

NameTypeDescription
keysRecordKey[]The (nameHash, version) lookups.

Returns

NameTypeDescription
recordsRecord[]The records, records[i] for keys[i].

latestMany

Batch latest: the record each name's latest pointer flags, in order.

Reverts LatticeRegistry__LatestUnset on the first name whose pointer was never set.

function latestMany(bytes32[] calldata nameHashes) external view returns (Record[] memory records);

Parameters

NameTypeDescription
nameHashesbytes32[]The curated name keys.

Returns

NameTypeDescription
recordsRecord[]The records, records[i] for nameHashes[i].

getSelectors

Live-read and drift-verify the facet's selectors for (nameHash, version).

Reverts LatticeRegistry__RecordNotFound if unregistered, LatticeRegistry__CodeDrift if facet.codehash no longer equals the pinned codehash, and LatticeRegistry__SelectorDrift if keccak256(exportSelectors()) no longer equals the pinned selectorsHash. A facet that reverts, runs out of its gas budget, returns oversized or malformed data, or lost its code is caught by the code/selector pins with one of those two errors, so the call never returns a stale cut. Then unpacks the blob to bytes4[].

function getSelectors(bytes32 nameHash, uint64 version) external view returns (bytes4[] memory selectors);

Parameters

NameTypeDescription
nameHashbytes32
versionuint64

Returns

NameTypeDescription
selectorsbytes4[]

getCut

Live-read, drift-verify, and assemble a ready-to-apply Add FacetCut for (nameHash, version).

Same code + selector drift verification as getSelectors; the returned cut uses FacetCutAction.Add.

function getCut(bytes32 nameHash, uint64 version) external view returns (FacetCut memory cut);

Parameters

NameTypeDescription
nameHashbytes32
versionuint64

Returns

NameTypeDescription
cutFacetCut

nameHash

Compute the registry key for a canonical facet name: keccak256(bytes(name)).

For callers interacting DIRECTLY with the contract (Etherscan, etc.) so they need not pre-hash a name off-chain. The registry hashes the RAW string and applies NO prefix, so the string overloads below and the bytes32 overloads are interchangeable when passed the same full canonical name. By CONVENTION Lattice facets are registered under "lattice.&lt;FacetName>" (e.g. "lattice.ERC20").

function nameHash(string calldata name) external pure returns (bytes32);

Parameters

NameTypeDescription
namestringThe full canonical facet name.

Returns

NameTypeDescription
<none>bytes32The bytes32 nameHash used by every Tier-B function.

register

register by canonical name string instead of its precomputed hash (owner-only).

function register(string calldata name, uint64 version, address facet) external;

Parameters

NameTypeDescription
namestring
versionuint64
facetaddress

setLatest

setLatest by canonical name string (owner-only).

function setLatest(string calldata name, uint64 version) external;

Parameters

NameTypeDescription
namestring
versionuint64

get

get the exact record for a canonical name string.

function get(string calldata name, uint64 version) external view returns (Record memory record);

Parameters

NameTypeDescription
namestring
versionuint64

Returns

NameTypeDescription
recordRecord

latest

latest record for a canonical name string.

function latest(string calldata name) external view returns (Record memory record);

Parameters

NameTypeDescription
namestring

Returns

NameTypeDescription
recordRecord

getSelectors

getSelectors for a canonical name string.

function getSelectors(string calldata name, uint64 version) external view returns (bytes4[] memory selectors);

Parameters

NameTypeDescription
namestring
versionuint64

Returns

NameTypeDescription
selectorsbytes4[]

getCut

getCut for a canonical name string.

function getCut(string calldata name, uint64 version) external view returns (FacetCut memory cut);

Parameters

NameTypeDescription
namestring
versionuint64

Returns

NameTypeDescription
cutFacetCut

owner

The current owner of the curated Tier-B namespace.

function owner() external view returns (address);

Returns

NameTypeDescription
<none>address

pendingOwner

The address that may call acceptOwnership to complete a pending handover (0 if none).

function pendingOwner() external view returns (address);

Returns

NameTypeDescription
<none>address

transferOwnership

Begin a two-step ownership handover to newOwner (owner-only; pass address(0) to cancel).

function transferOwnership(address newOwner) external;

Parameters

NameTypeDescription
newOwneraddress

acceptOwnership

Complete a pending ownership handover (pending-owner-only).

Reverts LatticeRegistry__NotPendingOwner when no handover is pending (pendingOwner == 0, which is also the state a cancelled handover leaves), so the owner can never become address(0).

function acceptOwnership() external;

Events

Attested

Emitted the first time a runtime codehash is attested into the Tier-A resolver.

event Attested(bytes32 indexed codehash, address indexed deployed);

Parameters

NameTypeDescription
codehashbytes32The runtime codehash that now resolves to deployed.
deployedaddressThe first address seen carrying codehash (first-write-wins).

Registered

Emitted when a new curated (nameHash, version) record is written.

event Registered(
        bytes32 indexed nameHash, uint64 indexed version, address indexed facet, bytes32 codehash, bytes32 selectorsHash
    );

Parameters

NameTypeDescription
nameHashbytes32The curated name key.
versionuint64The semver-packed version key.
facetaddressThe canonical facet address.
codehashbytes32The facet's runtime codehash (also auto-attested into Tier A).
selectorsHashbytes32The pinned keccak256 of the facet's exported selector blob.

LatestSet

Emitted when the latest convenience pointer for a name is set or moved (rollbacks allowed).

event LatestSet(bytes32 indexed nameHash, uint64 indexed version);

Parameters

NameTypeDescription
nameHashbytes32The curated name key.
versionuint64The version now flagged as latest for nameHash.

OwnershipTransferStarted

Emitted when a two-step ownership handover is initiated by the current owner.

event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

Parameters

NameTypeDescription
previousOwneraddress
newOwneraddress

OwnershipTransferred

Emitted when ownership is transferred (construction, and on acceptOwnership).

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

Parameters

NameTypeDescription
previousOwneraddress
newOwneraddress

Errors

LatticeRegistry__ZeroAddress

Thrown when a constructor / handover would set a zero owner.

error LatticeRegistry__ZeroAddress();

LatticeRegistry__Unauthorized

Thrown when a non-owner calls an onlyOwner function.

error LatticeRegistry__Unauthorized(address caller);

Parameters

NameTypeDescription
calleraddress

LatticeRegistry__NotPendingOwner

Thrown when an account other than the pending owner calls acceptOwnership.

error LatticeRegistry__NotPendingOwner(address caller);

Parameters

NameTypeDescription
calleraddress

LatticeRegistry__EmptyCode

Thrown when attest / register targets an address with no runtime code (EOA / empty — I4).

error LatticeRegistry__EmptyCode(address target);

Parameters

NameTypeDescription
targetaddress

LatticeRegistry__InvalidVersion

Thrown when register uses the reserved sentinel version 0 (0.0.0).

error LatticeRegistry__InvalidVersion();

LatticeRegistry__RecordExists

Thrown when register would overwrite an existing (nameHash, version) record (I1).

error LatticeRegistry__RecordExists(bytes32 nameHash, uint64 version);

Parameters

NameTypeDescription
nameHashbytes32
versionuint64

LatticeRegistry__RecordNotFound

Thrown when a view / setLatest references a (nameHash, version) that was never registered.

error LatticeRegistry__RecordNotFound(bytes32 nameHash, uint64 version);

Parameters

NameTypeDescription
nameHashbytes32
versionuint64

LatticeRegistry__LatestUnset

Thrown when latest is read for a name whose pointer was never set.

error LatticeRegistry__LatestUnset(bytes32 nameHash);

Parameters

NameTypeDescription
nameHashbytes32

LatticeRegistry__NotERC8153

Thrown when a curated facet does not satisfy the ERC-8153 selector contract at register (call reverts or runs out of its 100,000-gas budget / returns fewer than 64 or more than 8,192 bytes / an encoding abi.decode(ret, (bytes)) would reject / empty selectors / length not a multiple of 4 / duplicates / a blob that self-includes exportSelectors() (0x0ef22643), which must never be cut).

error LatticeRegistry__NotERC8153(address facet);

Parameters

NameTypeDescription
facetaddress

LatticeRegistry__SelectorDrift

Thrown by the live-read views when keccak256(exportSelectors()) no longer equals the pinned selectorsHash — an impure or mutated facet (invariant I2 for selectors). A live read that fails the bounds LatticeRegistry__NotERC8153 lists (revert, gas, size, malformed encoding) is also reported as drift.

error LatticeRegistry__SelectorDrift(address facet);

Parameters

NameTypeDescription
facetaddress

LatticeRegistry__CodeDrift

Thrown by the live-read views when the facet's current codehash no longer equals the codehash pinned at registration — defends against a metamorphic (CREATE2 + selfdestruct + redeploy) address swap that keeps exportSelectors() byte-identical (invariant I2 for code).

error LatticeRegistry__CodeDrift(address facet);

Parameters

NameTypeDescription
facetaddress

Structs

Record

A curated Tier-B facet record, immutable once written (invariant I1).

struct Record {
        address facet;
        uint64 version;
        uint48 registeredAt;
        bytes32 codehash;
        bytes32 selectorsHash;
    }

Properties

NameTypeDescription
facetaddressThe canonical facet address whose selectors are pinned by this record.
versionuint64Semver packed uint64 (major&lt;&lt;48 | minor&lt;&lt;24 | patch); opaque ordered key.
registeredAtuint48Block timestamp of registration (uint48 — good past the year 8·10^6).
codehashbytes32facet.codehash captured at registration (also mirrored into Tier A).
selectorsHashbytes32keccak256 of the tightly packed selector blob returned by
IERC8153(facet).exportSelectors() at registration — the drift pin the live-read views check.

RecordKey

One lookup for getMany: a curated name and the version to read.

struct RecordKey {
        bytes32 nameHash;
        uint64 version;
    }

Properties

NameTypeDescription
nameHashbytes32The curated name key.
versionuint64The semver-packed version, or 0 to read the record latest(nameHash) points at.