OpenZeppelin / OpenZeppelin/compact-contracts
N-05: Unbounded Per-Domain Supply Keys Can Create Persistent, Irreclaimable State Entries
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 55
- Forks
- 29
- Avg merge
- 5d 7h
- Merged PRs (30d)
- 25
Description
N-11 Unbounded Per-Domain Supply Keys Can Create Persistent, Irreclaimable State Entries
Severity: Note
Source: Midnight Foundation #02 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
NativeShieldedTokenPublicSupplyCore records its figures in two maps keyed by a 32- byte domain, and both update circuits write to them with a plain insert on whatever key reaches them, when recording an amount minted and when recording an amount burned. The key space is the full 32-byte range with no registration step or allowlist narrowing it, and neither circuit tests the amount against zero. A zero amount still reaches the insert, which writes the total back unchanged, and where the key has not been seen before that write adds an entry whose stored figure is zero, recording that a domain exists and nothing else. The two circuits differ in how easily that happens. Recording a burn requires the amount to fit within the domain's minted total, so on a key with no minted history only a zero amount gets through, whereas recording an amount minted checks only that the running total will not overflow before it writes and so accepts any amount on any key. Nothing in the module removes an entry once written, and the getters that answer a query through a membership test and a lookup therefore work against a structure that only grows.
Validation of the key rests entirely on the contract composing the extension. The per-domain wrapper passes its domain argument straight through and asserts nothing of its own, and the core's own guidance places the correctness of that argument with the consumer. A composition that exposes a supply path carrying a caller-chosen domain therefore lets each fresh value add state that neither the extension nor the consuming contract can reclaim, which raises the cost of every later supply read and write against a token whose accounting was expected to occupy one entry per token type.
Consider skipping the write when the amount is zero, so that a call which changes no total leaves no state behind, which on the burn side removes the only route to an entry under a domain that was never minted. Consider also confining the key space to domains the consuming contract registers or derives from a canonical token identifier, and recording in the wrapper documentation that a caller-chosen domain reaching these circuits produces persistent state with no route to reclaim it.
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 NativeShieldedTokenPublicSupplyCore.compact, especially the mint and burn update circuits, state maps, and getter circuits cited in the issue, then inspect NativeShieldedTokenFamilyPublicSupply.compact and its wrapper documentation. Verify how zero amounts and caller-chosen domains affect state, and define completion as preventing unnecessary persistent entries or clearly constraining and documenting the key space, with regression coverage for the affected paths.
Written by the indexing model from the issue text.
Assessment
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100