feat(deployer): support diamond selector removals on upgrade - #592
feat(deployer): support diamond selector removals on upgrade#592clawdbot-glitch003 wants to merge 2 commits into
Conversation
The deployer's upgrade path (get_facet_cuts) only computed Replace + Add, so a function dropped from a facet's source stayed routed to its old facet and remained callable after propose-update/update (surfaced reviewing the #575 backfillPkpOwners removal). Teach the deployer to Remove selectors, safely. - plan_removals(): a selector is removed only if it is BOTH listed in a committed manifest AND detected as orphaned by the upgrade (on a re-installed facet's old address, absent from every new managed facet ABI). Core DiamondCut/Loupe selectors are never candidates. Unlisted orphans warn (stay callable); listed-but-not-orphaned entries are refused; a missing/mismatched selector guard blocks typos. So a partial/bad build can never silently strip a live function. - build_diamond_update() emits a single Remove FacetCut (facetAddress 0x0) for the approved selectors; both `update` and `propose-update` benefit. - New --removals arg (default diamond-removals.json; missing file = no-op, backward compatible). Wired into the Makefile base/sepolia recipes. - diamond-removals.json declares backfillPkpOwners (0x41275609) so the #575 cleanup upgrade actually drops it on-chain. - 6 unit tests for plan_removals + a guard that the backfill selector is 0x41275609. Pairs with the facet-source cleanup PR (removes backfillPkpOwners from WritesFacet); the removal fires exactly when the deployed ABI omits it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Differential Security Review — automatedBase:
Overall risk: LOW — Recommendation: APPROVE This PR adds a manifest-gated selector-removal feature to the diamond-cut deployer ( Top findings (all INFO, non-blocking):
Full report: 🤖 Generated by automated differential-review skill |
CI cargo fmt --check failed on the plan_removals test asserts. No logic change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up from the #590 code review. The Rust deployer's upgrade path only computed Replace + Add cuts (
get_facet_cuts), never Remove — so a function deleted from a facet's source stayed routed to its old facet and remained callable afterpropose-update/update. That's a latent gap for any selector removal; it surfaced because #590 removesbackfillPkpOwnersbut the normal deploy flow wouldn't actually drop it on-chain.Design: explicit manifest + safe diff detection
A selector is Removed only if it is both:
diamond-removals.json), andplan_removals()(pure, unit-tested) enforces this. Consequences:build_diamond_updateemits a singleRemoveFacetCut (facetAddress 0x0) for the approved selectors, so bothupdateandpropose-update(Safe flow) get it.Wiring
--removals <path>arg, defaultdiamond-removals.json; missing file = no removals (backward compatible). Added to the Makefile base/sepolia update + propose recipes.diamond-removals.jsondeclaresbackfillPkpOwners(0x41275609), so the refactor(contracts): remove completed #575 PKP-owner backfill machinery #590 cleanup upgrade drops it on-chain automatically once the WritesFacet build no longer declares it. (If run before that, it's detected as not-orphaned and skipped with a warning — safe either way.)Tests
6 unit tests for
plan_removals(orphan removed; unlisted orphan warned; non-orphan refused; absent selector skipped; mismatch guard; and a guard assertingselector_of("backfillPkpOwners(address[],uint256[])") == 0x41275609).cargo build,cargo test,cargo clippyclean.Relationship to the other PRs
backfillPkpOwners+owner==0fallback from the contracts) is a separate PR.🤖 Generated with Claude Code