feat(eligibility): superAdmin-only writes + v4 upgrade script#167
Merged
Conversation
Replaces `onlyHatAdmin(hatId)` with `onlySuperAdmin` on every EligibilityModule write path (setWearerEligibility, setDefaultEligibility, clearWearerEligibility, setBulkWearerEligibility, batchSetWearerEligibility, createHatWithEligibility, registerHatCreation, updateHatMetadata). Hats hierarchy now only matters for vouching (via `combineWithHierarchy`); previously it also let any hat-admin in the chain directly mutate eligibility, which was a vouching-bypass path — a Delegate could either force-add a wearer or force-revoke an existing one without governance. Also retires the no-longer-reachable `NotAuthorizedAdmin` error and tightens `hasAdminRights` to only return true for the superAdmin (was returning true for Hats-hierarchical admins who, post-change, can't actually do anything). Tests: 12 new regression tests in a dedicated SUPER-ADMIN GATING section (every gated function gets a negative test against a hierarchical admin, plus end-to-end "hierarchy admin cannot bypass vouching via direct Hats.mintHat" and positive vouching tests with combineWithHierarchy on/off). Existing tests that previously asserted hierarchical-admin writes succeeded were rewritten to assert they now revert with NotSuperAdmin, with the superAdmin path covering the success cases. Upgrade: UpgradeEligibilitySuperAdminLockdown.s.sol provides the standard 3-step cross-chain rollout (Step1 Gnosis DD deploy → Step2 Arbitrum DD deploy + Hyperlane beacon dispatch → Step3 Gnosis verify) using v4 (probed FREE on both chains, both surfaces, deterministic addr 0x881330E39EbD920e0406D066cf775168c3726239). DryRun_GnosisUpgrade simulates against KUBI's live module on a Gnosis fork under FOUNDRY_PROFILE=production and asserts: storage preservation, hierarchy admin (caleb wearing EXECUTIVE_HAT) blocked from setWearer/setDefault/updateMeta with NotSuperAdmin, KUBI_EXECUTOR round-trips (true,true)→(false,false)→cleared, unaffiliated EOA blocked, hasAdminRights tightened correctly. All 9 checks pass. No storage layout changes — safe upgrade for KUBI / Test6 / Poa / any other org currently using the EligibilityModule beacon. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hudsonhrh
added a commit
that referenced
this pull request
Jun 1, 2026
… wins Reconciles main's TaskManager v4/v5 + EligibilityModule lockdown (#159, #167, #169) with the capability-hat refactor. Per direction, the capability-hat model is canonical and main's new features were re-expressed in it. Resolution highlights: - TaskManager Layout: kept main's foldersRoot/organizerHatIds in their on-chain slots (live-org upgrade safety), appended cap-hat gate fields after, plus new budgetHat/editMetaHat/editFullHat. Append-only vs deployed v5 layout. - v4 editable budgets: BOUNTY_CAP/PROJECT_CAP now gated by _requireBudgetEditor -> BUDGET capability hat (executor OR cap-hat), not the bitmask. - v5 post-claim edits: updateTask (EDIT_FULL) + updateTaskMetadata (EDIT_META) gate via _hasCap, not _permMask. - Folders kept verbatim (orthogonal; organizerHatIds stays a HatManager array gate). - bootstrapGlobalPerms re-expressed as a deployer-time bulk global gate-hat setter (expands each mask into per-gate cap-hat assignments); deleted the dead bitmask helpers (_permMask, _syncPermissionHat, refcount machinery). - setConfig(ROLE_PERM)/setProjectRolePerm extended to BUDGET/EDIT_META/EDIT_FULL, kept strict single-bit (InvalidCapMask). - EligibilityModule: kept #167 superAdmin-only lockdown; the sole exception is setWearerEligibility via onlySuperAdminOrRevoker (RoleBundleHatter cascade). - OrgDeployer: DeploymentParams keeps both capabilityHats/roleBundles and taskManagerPerms (complementary). Re-added _resolveRoleIndicesToHatIds. - Added lens variant 12 ([budgetHat, editMetaHat, editFullHat]) for the new gates. Security fix surfaced by the merge (pre-existing on the branch; DeployerTest was previously excluded from verification): RoleBundleHatter.mintRole's pre-mint eligibility reset unconditionally set wearers eligible, bypassing the vouching gate (defaults.eligible=false) on self-service QuickJoin. Now gated on hasSpecificWearerRules so it only re-enables previously-revoked wearers (preserves re-mint-after-revoke) and never overrides a default-driven gate for fresh wearers. Tests: 1423 passing / 0 failing / 16 skipped (obsolete bitmask tests). Main-era bitmask tests adapted to capability-hat behavioral assertions. forge build/fmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
onlyHatAdmin(hatId)withonlySuperAdminon everyEligibilityModulewrite path so only the org's Executor can mutate eligibility — Hats hierarchy now only matters for vouching (viacombineWithHierarchy), closing the previous vouching-bypass where any hat-admin could force-add or force-revoke wearers directly. Retires the unreachableNotAuthorizedAdminerror and tightenshasAdminRightsto only return true for the superAdmin.Hats.mintHat" test and combineWithHierarchy on/off vouching cases. Rewrote the pre-existing hierarchy-admin tests to assertNotSuperAdminreverts and route success cases throughsetup.exec.script/upgrades/UpgradeEligibilitySuperAdminLockdown.s.solwith the standard 3-step cross-chain rollout (Step1 Gnosis DD deploy → Step2 Arbitrum DD deploy + Hyperlane beacon dispatch → Step3 Gnosis verify) atv4— probed FREE on both registry + CREATE2 surfaces on both chains, deterministic addr0x881330E39EbD920e0406D066cf775168c3726239.DryRun_GnosisUpgraderuns end-to-end against KUBI's live module on a Gnosis fork underFOUNDRY_PROFILE=production— all 9 assertions pass (storage preservation, caleb-as-hierarchical-admin blocked withNotSuperAdmin, KUBI_EXECUTOR round-trips(true,true)→(false,false)→cleared,hasAdminRightstightened correctly).Test plan
forge build(default profile)forge fmtforge test --match-contract DeployerTest --match-test "testHierarchyAdmin|testUnaffiliatedAddress"— 12/12 PASStestEligibilityModuleAdminHatSystem,testExecutiveGivesHatsToTwoPeopleThenTurnsOffOne,testVouchingSystemHybridMode,testRegisterHatCreation*,testOption1SingleTransactionCreateAndMint— 22/22 PASSforge test --match-contract BatchFunctionsTest— 36/36 PASSFOUNDRY_PROFILE=production forge script .../UpgradeEligibilitySuperAdminLockdown.s.sol:DryRun_GnosisUpgrade --fork-url gnosis— ALL DRY-RUN CHECKS PASSED🤖 Generated with Claude Code