OpenZeppelin / OpenZeppelin/compact-contracts
N-02: Repeated Grants Can Exhaust Role Capacity And Permanently Block New Grants
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 55
- Forks
- 29
- Avg merge
- 5d 7h
- Merged PRs (30d)
- 25
Description
N-09 Repeated Grants Can Exhaust Role Capacity And Permanently Block New Grants
Severity: Note
Source: Midnight Foundation #02 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
Role grants are stored as leaves of _operatorRoles, a MerkleTree<20, RoleCommitment>, and the module header records that this gives a fixed capacity of 2^20 slots. Revocation does not reclaim a slot, since it records a nullifier in a separate set and leaves the original leaf in place.
What the header does not record is that a repeated grant of the same pairing consumes a further slot each time. The grant branch of _updateRole inserts the computed commitment unconditionally, with no check for a commitment that is already present, so issuing an identical role and account pairing many times exhausts capacity as surely as issuing distinct ones. Once the tree is full, no role can ever be granted again, and no path exists to recover a slot. Reaching that state requires the admin-gated grant circuit to be driven roughly a million times, so the realistic route is automation repeating a grant it believes idempotent rather than an unprivileged caller. An attacker having access to the leaked admin key may become motivated to do so and cause denial-of-service on the contract.
Consider deduplicating before insertion, for instance by recording issued commitments in an auxiliary set and skipping an insert whose commitment is already present, which also makes a repeated grant idempotent as a consumer would expect. It is further advisable to expose the number of consumed slots, since the Merkle tree ledger type offers no occupancy accessor and a consumer therefore has no way to observe how much capacity remains.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with contracts/src/access/ShieldedAccessControl.compact, especially _operatorRoles and the grant branch of _updateRole around lines 577-592. Read how commitments are inserted and how revocation is represented; done means repeated grants do not consume new capacity and consumers can observe consumed slots.
Written by the indexing model from the issue text.
Assessment
- Domain
- authorization, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100