Skip to main content

EntityRegistry Fuzz Coverage

Vertical entrypoint: test/fuzzing/FuzzEntityRegistryIntegrity.sol Invariants: test/fuzzing/properties/Properties_ER.sol Descriptions: test/fuzzing/properties/PropertiesDescriptions.sol

Scope

Direct exercise of EntityRegistry entity registration, entity status changes, pending account registration request/acceptance, immediate admin account registration, account status changes, account removal and transfer, entity-manager toggling, authority and metadata updates, account role-flag updates, entity-type lifecycle, getter/validation views, RBAC overloads, and initializer-lock behavior. The harness defines the three supported entity types (BROKER, NATURAL_PERSON, DEUSS_PROTOCOL), pre-registers the core protocol wallets plus the three marketplace users, then drives extra registry state through dedicated fuzz-only pools:

  • FUZZ_WALLETS (0x40000..0x80000) are the only wallets this vertical registers, removes, and transfers.
  • MANAGERS (0x90000..0xB0000) are the caller/subject pool for manager-sensitive actions.

The harness itself holds all EntityRegistry roles, but requestAccountRegistration deliberately pranks as a selected manager so the vertical exercises the real entity manager or admin authorization path. Pending requests are tracked as bounded (account, entityId) pairs, which lets later fuzz steps accept, overwrite, revoke, or stale-check them independently. setEntityStatus is restricted to fuzz-created entities, so the setup entities that back Marketplace, EscrowManager, and the core actors cannot be disabled accidentally.

Handlers

EntrypointCallerTargetPurpose
fuzz_registerEntityharness (address(this), ONBOARDING)entityRegistry.registerEntity(bytes32,uint256,string)Create a new enabled entity with one of the defined type IDs
fuzz_setEntityStatusharness (address(this), GUARD)entityRegistry.setEntityStatusEnable or disable a fuzz-created entity
fuzz_registerAccountselected manager (MANAGER1..3) then selected walletentityRegistry.requestAccountRegistration + entityRegistry.acceptAccountRegistrationExercise the entity-manager authorization path and account-side acceptance for wallet onboarding
fuzz_requestAccountRegistrationselected manager (MANAGER1..3)entityRegistry.requestAccountRegistrationCreate pending account-registration requests without accepting them in the same handler
fuzz_acceptAccountRegistrationselected pending walletentityRegistry.acceptAccountRegistrationAccept a pending request created by an earlier fuzz step
fuzz_requestAccountRegistrationOverwritetwo selected managersentityRegistry.requestAccountRegistration twice for one pairAssert repeated requests for the same (account, entityId) overwrite pending requester/role data
fuzz_requestAccountRegistrationMultipleEntitiesselected managerentityRegistry.requestAccountRegistration for two entitiesAssert one account can hold independent pending requests for multiple entities
fuzz_pendingAccountRegistrationInvalidInputsselected manager / selected walletrequest/accept negative pathsCover zero account, zero/missing/disabled entity, registered-account, and missing-pending reverts
fuzz_acceptAccountRegistrationAlreadyRegisteredselected walletentityRegistry.acceptAccountRegistrationAssert acceptance reverts while the account is already registered elsewhere
fuzz_requesterRevocationBeforeAcceptanceselected manager then selected walletsetEntityManager(false) + acceptAccountRegistrationAssert acceptance reverts if the original requester is no longer manager/admin
fuzz_stalePendingRegistrationAfterRemovalselected manager then selected walletrequest, direct register, remove, acceptCover pending requests that survive direct registration and account removal
fuzz_adminRegisterAccountharness (address(this), ADMIN_ROLE)entityRegistry.registerAccountExercise immediate admin account registration as a distinct path
fuzz_registerAccountUnauthorizednon-admin fuzz actorentityRegistry.registerAccountAssert non-admin callers cannot use immediate registration
fuzz_registerAccountInvalidInputsharness (address(this), ADMIN_ROLE)entityRegistry.registerAccount negative pathsCover zero account, zero/missing/disabled entity, and already-registered account reverts
fuzz_setAccountStatusharness (address(this), GUARD)entityRegistry.setAccountStatusEnable or disable a registered fuzz wallet
fuzz_removeAccountharness (address(this), WALLET_TRANSFER)entityRegistry.removeAccountRemove a registered fuzz wallet and free it for reuse
fuzz_transferAccountToEntityharness (address(this), WALLET_TRANSFER)entityRegistry.transferAccountToEntityRelink a registered fuzz wallet to a different enabled entity
fuzz_setEntityManagerharness (address(this), ADMIN_ROLE)entityRegistry.setEntityManagerToggle whether a manager address is enabled for an entity
fuzz_registerEntityWithAccessharness (address(this), ONBOARDING)entityRegistry.registerEntity(bytes32,uint256,string,address,address[])Cover the authority/managers overload and assert the configured authority + managers are stored
fuzz_registerEntityBatchharness (address(this), ONBOARDING)entityRegistry.registerEntityBatchRegister two entities through the batch overload and verify both are persisted
fuzz_setEntityMetadataharness (address(this), ONBOARDING)entityRegistry.setEntityMetadataUpdate an enabled entity's metadata reference
fuzz_setEntityAuthorityharness (address(this) then current authority)entityRegistry.setEntityAuthorityCover both admin-driven authority assignment and authority-driven rotation
fuzz_setAccountRoleFlagsharness (address(this), ONBOARDING)entityRegistry.setAccountRoleFlagsUpdate roleFlags on a known-good registered account
fuzz_entityRegistryViewSurfaceharness (address(this), GUARD / view)getters + canTransfer / canApproveProbe success and expected-revert view paths, zero-address validation branches, and disabled-account / disabled-entity validation branches
fuzz_entityTypeSurfaceharness (address(this), ENTITY_TYPE_MANAGER)defineEntityType, freezeEntityType, getEntityTypeMetaAssert zero-name type definitions revert, define a fresh entity type, freeze it, and assert frozen updates revert
fuzz_entityRegistryAdminSurfaceharness (address(this), owner)grantRoles(address,uint256) + grantRoles(address[],uint256)Cover both RBAC grant overloads plus invalid-role rejection
fuzz_entityRegistryInitializeAgainharness (address(this))entityRegistry.initializeAssert the live proxy cannot be initialized twice
fuzz_entityRegistryFreshDeploymentharness (address(this))new EntityRegistry() + locked initializeCover constructor-time initializer locking on a fresh implementation deployment
fuzz_entityAccountSwapAndPopSurfaceharness (address(this), ONBOARDING / WALLET_TRANSFER)register/transfer/remove account lifecycleForce the swap-and-pop branch in entity.accounts[], then remove the remaining account

fuzz_registerAccount keeps the original combined request-and-accept lifecycle for backwards-compatible coverage. The newer pending-registration handlers split request and acceptance across separate fuzz calls, and targeted handlers pin down overwrite, multi-pending, requester-revocation, already-registered, and stale-after-removal behavior. The immediate registerAccount admin path is covered separately under ER-110 .. ER-115.

Invariants

registerEntity — group 10

Checked in registerEntityPostconditions for fuzz_registerEntity.

IDConditionChecked
ER-10Registered entity is stored with status = ENABLED and the selected typeIdon success
ER-11Newly registered entity starts with an empty accounts[] arrayon success
ER-12No unexpected revertson revert

setEntityStatus — group 20

Checked in setEntityStatusPostconditions for fuzz_setEntityStatus.

IDConditionChecked
ER-20Entity status matches the requested ENABLED / DISABLED valueon success
ER-21No unexpected revertson revert

requestAccountRegistration + acceptAccountRegistration — group 30

Checked in registerAccountPostconditions for fuzz_registerAccount.

IDConditionChecked
ER-30Accepted account is stored as ENABLED, linked to the selected entity, and marked as registeredon success
ER-31Target entity account count increments by exactly 1 after acceptanceon success
ER-32No unexpected reverts when the selected requester is already an enabled manager for the entityon revert (managerAuthorized = true)
ER-33If the selected requester is not an enabled manager, the request must revert with ER__NotEntityManagerOrAdminon revert (managerAuthorized = false)

Pending account registration — groups 92-107

Checked in requestAccountRegistrationPostconditions, acceptAccountRegistrationPostconditions, and targeted pending handlers.

IDConditionChecked
ER-92Pending request stores the requested role flags and requesterrequest success
ER-93Requesting pending registration does not register the account or change entity account countrequest success
ER-94Re-requesting the same (account, entityId) overwrites pending requester/role dataoverwrite handler
ER-95One account can have independent pending requests for two entitiesmulti-entity handler
ER-96Unauthorized requesters revert with ER__NotEntityManagerOrAdminrequest revert
ER-97Zero account requests revert with ZeroAddressrequest negative surface
ER-98Zero, missing, and disabled entities revert with their specific entity-state selectorrequest negative surface
ER-99Requests for already registered accounts revert with ER__AccountAlreadyRegisteredrequest negative surface
ER-100Accepting a pending request registers the wallet and applies pending role flagsaccept success
ER-101Accepting a pending request increments entity account countaccept success
ER-102Accepted pending request is deletedaccept success
ER-103Accepting without a pending request reverts with ER__AccountRegistrationNotPendingaccept negative surface
ER-104Accepting while already registered reverts with ER__AccountAlreadyRegisteredaccept negative surface
ER-105Accepting after requester manager revocation reverts with ER__NotEntityManagerOrAdminrevocation handler
ER-106Failed accept after requester revocation leaves pending request data intactrevocation handler
ER-107Pending request survives direct registration and account removal until it is acceptedstale-removal handler

setAccountStatus — group 40

Checked in setAccountStatusPostconditions for fuzz_setAccountStatus.

IDConditionChecked
ER-40Account status matches the requested ENABLED / DISABLED valueon success
ER-41No unexpected revertson revert

removeAccount — group 50

Checked in removeAccountPostconditions for fuzz_removeAccount.

IDConditionChecked
ER-50Removed account is no longer registered (status = NONE)on success
ER-51Source entity account count decreases by exactly 1on success
ER-52No unexpected revertson revert

transferAccountToEntity — group 60

Checked in transferAccountPostconditions for fuzz_transferAccountToEntity.

IDConditionChecked
ER-60Transferred account points to the new entity and remains registeredon success
ER-61Old entity account count decreases by exactly 1on success
ER-62New entity account count increments by exactly 1on success
ER-63No unexpected revertson revert

setEntityManager — group 70

Checked in setEntityManagerPostconditions for fuzz_setEntityManager.

IDConditionChecked
ER-70isEntityManager(entityId, manager) matches the requested booleanon success
ER-71No unexpected revertson revert

Auxiliary Coverage Surfaces

The handlers below are direct surface checks rather than lifecycle-transition groups:

IDHandlerCondition
ER-80fuzz_registerEntityWithAccessAccess overload stores the chosen authority and enabled managers while preserving base entity-registration invariants
ER-81fuzz_registerEntityBatch, fuzz_setEntityMetadataMetadata references are stored as expected
ER-82fuzz_setEntityAuthorityAuthority assignment/rotation stores the expected authority
ER-83fuzz_setAccountRoleFlagsAccount role flags store the requested bitmap
ER-84fuzz_entityRegistryViewSurfaceGetter/validation surface returns expected values for enabled accounts and zero-address transfer/approve allowances
ER-85fuzz_entityRegistryViewSurfaceDisabled accounts are rejected by account-gated approval checks
ER-86fuzz_entityRegistryViewSurfaceDisabled accounts/entities are rejected by account-gated transfer checks
ER-87fuzz_entityTypeSurfacedefineEntityType stores non-zero name/caps and starts unfrozen
ER-88fuzz_entityTypeSurfacefreezeEntityType stores the frozen flag
ER-89fuzz_entityRegistryAdminSurfacegrantRoles scalar and batch overloads grant the selected valid role
ER-90fuzz_entityRegistryViewSurface, fuzz_entityRegistryFreshDeploymentERC-165 support reports expected interfaces and rejects the invalid interface ID
ER-91fuzz_entityAccountSwapAndPopSurfaceAccount transfer/removal lifecycle preserves entity account indices and links through swap-and-pop paths
ER-110fuzz_adminRegisterAccountAdmin registerAccount immediately stores an enabled account with the requested entity and role flags
ER-111fuzz_adminRegisterAccountAdmin registerAccount increments the target entity account count
ER-112fuzz_registerAccountUnauthorizedNon-admin registerAccount reverts with Solady Unauthorized()
ER-113fuzz_registerAccountInvalidInputsAdmin registerAccount rejects zero account
ER-114fuzz_registerAccountInvalidInputsAdmin registerAccount rejects zero, missing, or disabled entity with the expected selector
ER-115fuzz_registerAccountInvalidInputsAdmin registerAccount rejects already registered accounts

fuzz_entityRegistryInitializeAgain and fuzz_entityRegistryFreshDeployment also assert initializer locking on the live proxy and on a freshly deployed implementation.

Cross-cutting invariants

These are re-evaluated after every successful EntityRegistry action.

IDCondition
ER-01For every tracked entity, each account in entity.accounts[] links back to that entity with the correct 1-based entityAccountIndex

Preconditions

Summary of the clamping and state-selection logic applied before each handler issues a protocol call. Full implementation in helper/preconditions/PreconditionsEntityRegistry.sol.

HandlerClamp rules
registerEntityentityId is deterministically derived as keccak256(abi.encodePacked(address(this), entityNonce)), then entityNonce increments. typeId is chosen from {BROKER_ENTITY, NATURAL_PERSON_ENTITY, DEUSS_PROTOCOL_ENTITY} via typeIdSeed % 3.
setEntityStatusEntity picked from FuzzEntityBucket.Existing, so only fuzz-created entities are reachable. Requested status is ENABLED when enable = true, otherwise DISABLED.
registerAccountWallet picked from FuzzAccountBucket.Free. Entity picked from EntityBucket.Enabled, so disabled entities are excluded. Requester is MANAGERS[managerSeed % MANAGERS.length]; successful requests are accepted by the wallet. The precondition records whether that manager is already enabled for the entity so postconditions can distinguish expected success from expected authorization failure.
requestAccountRegistrationWallet picked from FuzzAccountBucket.Free; pending-only requests leave that wallet in the free bucket until acceptance. Entity is picked from EntityBucket.Enabled; requester comes from MANAGERS.
acceptAccountRegistrationPending pair picked from the bounded pending-registration set. Postconditions classify failure by the contract's check order: entity enabled, account not registered, pending request exists, requester still manager/admin.
requestAccountRegistrationOverwriteWallet and entity are selected like requestAccountRegistration; two distinct managers are enabled before issuing two requests for the same pair.
requestAccountRegistrationMultipleEntitiesWallet is selected from FuzzAccountBucket.Free; two distinct enabled entities are selected; one manager is enabled for both before issuing the two requests.
pendingAccountRegistrationInvalidInputsUses one valid enabled entity/manager pair, plus a fresh disabled fuzz entity and a missing synthetic entity ID, to pin each request/accept validation selector.
acceptAccountRegistrationAlreadyRegisteredCreates a valid pending request, registers the same wallet directly into a different enabled entity, then asserts accept reverts before consuming the pending request.
requesterRevocationBeforeAcceptanceCreates a valid pending request, disables the requester manager flag, and asserts accept reverts while pending data remains.
stalePendingRegistrationAfterRemovalCreates a valid pending request, directly registers and removes the same account through a different entity, verifies the pending request remains, then accepts it.
adminRegisterAccountWallet picked from FuzzAccountBucket.Free; entity picked from EntityBucket.Enabled; role flags are passed through directly.
registerAccountUnauthorizedSame valid account/entity selection as adminRegisterAccount, but the call is pranked from FUZZ_ESCROW_UNAUTH_CALLER, which has no ADMIN_ROLE.
registerAccountInvalidInputsUses a selected free wallet, a valid enabled entity, a missing synthetic entity ID, a fresh disabled fuzz entity, and USER1 restored to enabled status for the already-registered branch.
setAccountStatusWallet picked from FuzzAccountBucket.Registered. Its linked entity must still be ENABLED, matching the protocol precondition for setAccountStatus. Requested status is ENABLED when enable = true, otherwise DISABLED.
removeAccountWallet picked from FuzzAccountBucket.Registered. The linked entityId is read from the current account record and used for before/after count assertions. Registered fuzz wallets are disjoint from the authority/manager subject pools, so the governance-account guard is covered by unit tests rather than this lifecycle fuzz path.
transferAccountToEntityWallet picked from FuzzAccountBucket.Registered. Source entity must be ENABLED. Destination entity is picked from EntityBucket.Enabled and must differ from the current entity. Registered fuzz wallets are disjoint from the authority/manager subject pools, so the governance-account guard is covered by unit tests rather than this lifecycle fuzz path.
setEntityManagerEntity picked from EntityBucket.Existing, so both setup entities and fuzz-created entities are eligible. Manager subject is MANAGERS[managerSeed % MANAGERS.length]; enabled is passed through directly.
registerEntityWithAccessReuses registerEntity's unique-ID generation and supported-type selection, then chooses a non-zero authority and two distinct non-zero manager addresses from the unregistered fuzz manager pool so bootstrap access assignments satisfy the registered-account lifecycle checks.
registerEntityBatchReuses the deterministic registerEntity ID/type derivation twice so both batch items are valid and unique.
setEntityMetadataEntity is picked from EntityBucket.Enabled; metadata is selected from a small fixed fuzz-only string set.
setEntityAuthorityEntity is picked from EntityBucket.Existing; the handler chooses two non-zero authority addresses distinct from the current authority so both the admin path and authority-rotation path stay valid.
setAccountRoleFlagsUses the always-registered core actor USER1, restoring its entity/account enabled state first if a previous fuzz action temporarily disabled it.
entityRegistryViewSurfaceUses the always-registered core actor USER1, restores it to a clean enabled state, toggles account/entity status only temporarily to probe negative validation branches, then restores the original enabled state before returning.
entityTypeSurfaceMints a fresh synthetic typeId from the harness nonce, first asserts that defineEntityType(typeId, 0, caps) reverts, then defines and freezes the type with a non-zero name.
entityRegistryAdminSurfaceGrants valid roles only to fuzz-only sink addresses (FUZZ_WALLET_4..FUZZ_WALLET_7) so the broader harness caller model is unchanged.
entityRegistryInitializeAgainNo state selection: it directly probes the already-initialized live proxy.
entityRegistryFreshDeploymentNo state selection: it deploys a fresh standalone implementation and checks that the constructor's _disableInitializers() lock is active.
entityAccountSwapAndPopSurfaceRequires two distinct wallets from FuzzAccountBucket.Free; it then registers both into a fresh source entity, transfers the first into a fresh target entity, and removes the second.

All clamp failures raise ClampFail(string), which the integrity layer accepts as a skip rather than a bug.