refactor(contracts): remove completed #575 PKP-owner backfill machinery - #590
clawdbot-glitch003 wants to merge 3 commits into
Conversation
The one-time backfill is complete on Base mainnet (all pre-existing PKPs bound; verified 0 unbound, 0 conflicts). Remove the now-spent migration machinery in this facet upgrade: - WritesFacet: drop backfillPkpOwners() and the PkpOwnerBackfilled event. - ViewsFacet.getWalletDerivation: remove the `owner == 0` compatibility fallback so ownership enforcement is unconditional (fail-closed). registerWalletDerivation always sets the owner when it writes pkpData and the backfill bound every legacy PKP, so a non-zero derivation always has a non-zero owner; owner == 0 now reverts instead of serving the path. - Delete the tasks/backfill-pkp-owners.ts hardhat task + its registration. - Keep getPkpOwnerMaster (ongoing audit) and the core first-owner binding. - Update Foundry tests + DiamondDeploy selectors, regenerate alloy bindings, update CHANGELOG. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔒 Automated Security Review (differential)Base:
Overall risk: LOW — no exploitable regressions found. This PR removes the completed #575 PKP-owner backfill migration (
F1 (INFO): the removed admin migration tool can no longer recover a PKP if the #575 backfill missed it — that would only cause a fail-closed revert (availability impact for the legitimate owner), not a security bypass. Recommend confirming operationally that the backfill ran against every pre-existing pkpId before deploying. Full report: |
Codex reviewRan an independent [P1] The diamondCut must include an explicit Remove — the deployer won't generate itConfirmed. The Rust deployer's Impact is low security-wise ( The Follow-up worth filing separately: the deployer can't remove any selector today. A general fix (compute Removes for vanished managed-facet selectors) is valuable but delicate — it must not touch the core DiamondCut/Loupe selectors — so it deserves its own tested PR, not this one. [P2] Foundry
|
Resolve hardhat.config.ts conflict: keep the new verify-basescan task import from main, drop the deleted backfill-pkp-owners import. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…helper parity Codex (gpt-6-astra) review follow-ups: - plan_removals: also treat a manifest-listed selector as orphaned when it sits on a "stranded" address whose every remaining selector is either re-installed or listed. Without this, an upgrade that ran once without the manifest left the selector alone on the old facet and every retry refused to remove it. Core facets can only qualify if the manifest lists all of their selectors. Two unit tests. - contract_deployer: an explicit --removals path that does not exist is now a hard error instead of silently meaning "no removals". - README: document --removals and its cwd-relative default. - DiamondDeploy.sol: add the missing transferChainSecuredAccountOwnership selector so the Foundry diamond matches WritesFacet's 21-function surface. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex review (gpt-6-astra), post-merge-with-mainRan Fixed in e89e22d
Operational, not code (documented in the PR body)
|
The one-time #575 PKP-owner backfill is complete on Base mainnet — all pre-existing PKPs are bound (verified 0 unbound, 0 multi-account conflicts, 0 bound-to-non-owner across all accounts). This removes the spent migration machinery in the next facet upgrade. Contract-only; none of the operational backfill tooling lands in the repo.
Changes
backfillPkpOwners()and thePkpOwnerBackfilledevent.owner == 0compatibility fallback — ownership enforcement is now unconditional (fail-closed).registerWalletDerivationalways sets the owner when it writespkpData, and the backfill bound every legacy PKP, so a non-zero derivation always has a non-zero owner; anowner == 0now reverts instead of serving the path.tasks/backfill-pkp-owners.tshardhat task and its registration (its on-chain target no longer exists).getPkpOwnerMaster(ongoing audits) and the core first-owner binding inregisterWalletDerivation.owner == 0test now asserts fail-closed),DiamondDeployselectors, CHANGELOG, and regenerated the alloy bindings.Supersedes #579 (closed — it also carried the operational Safe/EOA tooling, which we don't want in the repo now that the migration is done).
Verification
forge test50/50 ·cargo checkclean ·tscclean (only the pre-existing unrelatedverify-diamond-facets.tswarning) · no stray references to the removed function.Deploy note
This is a diamondCut that replaces WritesFacet and ViewsFacet (
getWalletDerivationbehavior change — a normal Replace, same selector).The
backfillPkpOwnersremoval (0x41275609) is handled by the normal deploy flow: #592 taught the deployer to emit manifest-gated Remove cuts, anddiamond-removals.jsonon main already lists this selector. Once this PR's WritesFacet build (which no longer declares it) is upgraded viapropose-update/update, the selector is detected as orphaned and removed in the same cut. No hand-written Remove entry is needed. (If the upgrade were run against a WritesFacet that still declares it, the deployer refuses the removal with a warning — safe either way.)Do it after the diamond's
configOperatoris restored from the temporary migration EOA back to the original, and after retiring that temp key.Precondition for every diamond this is applied to (Base, Sepolia, dev): the #575 backfill must already have bound every legacy PKP, because the
owner == 0read path is gone and so is the on-chain repair function. Base mainnet is verified complete. If a non-prod diamond still has an unbound legacy PKP, the owning account can self-heal without operator help:removeWalletDerivationthenregisterWalletDerivationwith the same path re-registers it and sets the owner binding (first-owner rule,existingOwner == 0). No key material is lost since the key is a stateless function of the path.Node-side cache:
get_wallet_derivationresults are cached for up to 300s per replica, so a derivation resolved just before the cut stays usable for at most that window. On Base this is moot (no owner-zero PKPs exist), so no cache flush is needed for prod.Review follow-ups in this PR
Independent Codex (gpt-6-astra) review + adversarial pass surfaced two code gaps, fixed here:
plan_removals: if an upgrade was once run without the manifest, the dropped selector ended up alone on the old facet address and every retry refused to remove it (the address was no longer "managed"). Listed selectors on such a stranded address are now removable, but only when every remaining selector on that address is either re-installed or listed, so a core facet can never qualify by accident. Two new unit tests.--removals: an explicitly passed manifest path that does not exist is now a hard error rather than silently meaning "no removals". README documents the flag and its cwd-relative default.DiamondDeployhelper: added the long-missingtransferChainSecuredAccountOwnershipselector so the test diamond matches WritesFacet's 21-function surface.🤖 Generated with Claude Code