A zero-friction manager for EIP-2535 Diamond upgrades.
This library detects what changed since your last deploy, syncs selectors from ABI, deploys whatever is needed, and executes one deterministic diamondCut. You focus on facet code—everything else is handled.
TL;DR: Change your facets → call
upgrade("<name>")→ done.
- Auto-discovers facets in
src/<name>/facets/**. - Auto-syncs selectors from
out/**(no hand-written bytes4 arrays). - Diffs “desired vs last manifest” to build Add / Replace / Remove plan.
- Deploys new/changed facets and executes
diamondCut(optionally with init). - Persists a manifest at
.diamond-upgrades/<name>/manifest.jsonafter a real broadcast. - Protects core (Cut / Loupe / Ownership) and rejects selector collisions.
project/
├─ src/
│ ├─ <name>/
│ │ ├─ facets/ # your facets
│ │ ├─ interfaces/
│ │ └─ libraries/
├─ .diamond-upgrades/
│ └─ <name>/
│ ├─ storage.json # TBA
│ ├─ facets.json # desired facets (selectors auto-synced)
│ └─ manifest.json # last on-chain snapshot
├─ out/ # Foundry artifacts
└─ foundry.toml
Enable FFI and allow the library to read/write manifests:
# foundry.toml
[profile.default]
ffi = true
fs_permissions = [
{ access = "read", path = "src" },
{ access = "read", path = "out" },
{ access = "read-write", path = ".diamond-upgrades" }
]
remappings = [
"diamond-foundry/=lib/diamond-foundry/src/"
]forge install montyp0x/diamond-foundryforge buildimport {DiamondUpgrades} from "diamond-foundry/DiamondUpgrades.sol";
DiamondUpgrades.deployDiamond(
"example",
DiamondUpgrades.DeployOpts({
owner: user,
opts: DiamondUpgrades.Options({unsafeLayout: false, allowDualWrite: false, force: false})
}),
DiamondUpgrades.InitSpec({target: address(0), data: ""})
);// No manual prepare step needed — discovery & selector sync run automatically.
address diamond = DiamondUpgrades.upgrade("example");The library compares the latest
.diamond-upgrades/example/manifest.jsonwith your desired facets, deploys what’s needed, executes a singlediamondCut, and writes an updated manifest.
-
Desired state lives in
.diamond-upgrades/<name>/facets.json:- each facet is referenced by artifact
File.sol:Contract, - selectors are always rebuilt from ABI in
out/**(so they never drift).
- each facet is referenced by artifact
-
Current state lives in
.diamond-upgrades/<name>/manifest.json:- diamond address, facet addresses, selector ↔ facet mapping, runtime bytecode hashes, history, and a deterministic
stateHash.
- diamond address, facet addresses, selector ↔ facet mapping, runtime bytecode hashes, history, and a deterministic
-
Planner creates a deterministic plan:
- Add (new selectors),
- Replace (same selector routed to new facet/bytecode),
- Remove (selector no longer desired).
-
Executor deploys / reuses facets and calls
diamondCut(init?).
- Core selectors are protected by default (Cut/Loupe/Ownership won’t be accidentally touched).
- Selector collisions across facets cause a clear revert.
- No-op upgrades keep facet addresses and
stateHashunchanged. - Manifests live at
.diamond-upgrades/<name>/manifest.json(per diamond name).
.diamond-upgrades/<name>/facets.json— desired facets & auto-synced selectors..diamond-upgrades/<name>/manifest.json— last known on-chain snapshot; used for diffing on the next upgrade..diamond-upgrades/<name>/storage.json— optional namespace/storage policy config (you can ignore it for now).
MIT.
Built for the Foundry ecosystem, inspired by EIP-2535 and the developer experience of OpenZeppelin Upgrades.