0004. Releases use raw-salt CREATE2: versioned facet salts, versionless registry and factory salts
- Status: Accepted for the salt scheme and the use of CREATE2. The choice of deployer is under review: #193 (open) proposes Arachnid's proxy on every chain instead of CreateX first.
- Date: salts and CreateX from #120 (2026-07-09); the Arachnid fallback from #189 (2026-10-07); recorded 2026-10-09
Context
A release deploys the LatticeRegistry, the
LatticeFactory and every facet in
FacetInventory. Integrators and Lattice Studio predict these
addresses offline, a half-finished release must be finishable by anyone, and nobody may be able to put
other code at a canonical address.
Options considered
- Raw-salt CREATE2. The address depends on the deployer contract, a fixed salt and the initcode hash, so it is the same on every chain with that deployer and commits to the bytecode.
- CREATE3 with a sender-guarded salt (CreateX). The address survives bytecode changes, but only the
pinned sender can deploy and the chain id is folded in, so addresses differ per chain. Lattice keeps it
for the adapter deployments in
DeployAdapters, not for releases. - Plain CREATE from a release key. Addresses depend on a nonce and a key.
Decision
- Salts are raw protocol strings with no deployer and no chain id:
- every facet:
keccak256("lattice.<Name>.<version>"), for examplekeccak256("lattice.ERC20.0.1.0")(DeployRelease.facetSalt, andBaseDeploy._facetwithLatticeVersion.VERSION); - registry:
keccak256("lattice.LatticeRegistry"), versionless (REGISTRY_SALT); - factory:
keccak256("lattice.LatticeFactory"), versionless (FACTORY_SALT).
- every facet:
- The deployer is CreateX's raw-salt CREATE2 (
0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed), which hashes the salt once more before use. On a chain where CreateX has no code and Arachnid's proxy (0x4e59b44847b379578588920cA78FbF26c0B4956C) has, the release uses the proxy, which uses the salt as given. CreateX wins wherever both exist.DeployReleaserefuses a chain with neither. - Every deploy is predict-then-skip, so a release run is idempotent and resumable by anyone.
Consequences
- Every release, patches included, publishes every facet at new addresses, because the version is in the salt. A diamond keeps the facets it was cut with until it is upgraded. A facet edit, NatSpec included, therefore moves no address that would not move anyway.
- The registry and factory addresses depend only on their initcode (plus the registry's
ownerargument and the factory'sregistryargument) and on the chain's deployer. Any change to their bytecode moves them, including a NatSpec edit, becausefoundry.tomlkeeps solc's default metadata hash. Changes to them, such as those #176 reviews, belong before the first canonical broadcast; after it, the versioning policy keeps patch releases from changing them. - A chain on Arachnid's proxy gets different addresses from a CreateX chain for the same release. #193 would make the proxy the only release deployer, which keeps the salts and moves every address once.
- A release reproduces only from the pinned compiler configuration at its tag. The Hedera profile changes the metadata hash and therefore the address.
Confirmation
DeployReleaseTest:test_SaltDerivationGoldenspins the salts,test_Release_IdempotentResumeandtest_Release_PartialResumeRegistersPredeployedFacetthe resume path,test_Release_RunsOnAnArachnidOnlyChainandtest_Release_RevertsWithoutAnyDeterministicDeployerthe deployer choice.
References
- REGISTRY_DEPLOYMENTS.md, the canonical reference, including why CREATE2 here and what changes an address
DeployRelease.s.sol,CreateXDeployer.sol- README: versioning and compatibility
- #120, #189, #193, #176