# Proposal ID collision and duplicate blocking

Uniqueness keys that omit lifecycle fields, or slates that accept duplicate ids, letting attackers squat or inflate

_Area: Smart contract access control and control flow. Seen in 3 disclosed reports; latest 2024-04-19. Updated 2026-10-23._

## How it works
A proposal's identity is a key: a name, a hash, or a slate of ids. The code assumes that key uniquely identifies one proposal and that whoever creates it first is the legitimate proposer. When the key omits a field that changes the proposal's meaning or lifecycle, two different proposals map to the same key. The first one stored wins; the second reverts or is silently treated as already existing. An attacker who can create the cheap or degenerate version first occupies the key and blocks the real proposal. A related sub-mechanism: when a slate or committee sums per-item budgets without deduplicating ids, repeated ids inflate the total and crowd out legitimate items.

## Where it shows up
- Uniqueness checks keyed on a name or hash that does not cover every user-supplied field (addresses, call numbers, descriptions, start/end blocks, deadlines).
- Proposal ids computed as a hash of a struct or argument list; compare the hashed fields against the fields the contract actually uses.
- Slate or committee selection that sums per-item budgets without deduplicating ids.
- Winning-slate checks that test membership only, not counts.
- Reserved suffixes or namespaces (e.g. a `_confirm` suffix) derived from user input and also used as a uniqueness guard.

## How to review
Identify the uniqueness key and list every field that affects proposal meaning or lifecycle. Put the hash inputs and the struct fields side-by-side and check for gaps. The invariant: two proposals that differ in any lifecycle-relevant field must produce different keys, and no key can be pre-occupied by a proposal the attacker controls to block a legitimate one. For slates, the invariant is that repeated ids cannot inflate a budget total and the winning slate is compared by exact multiset, not membership.

## Variants seen
- Ballot name omits key parameters, allowing name squatting to DoS proposals [E1](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-12.md).
- Proposal hash omits endBlock, allowing front-run with a degenerate proposal that blocks the real one [E2](https://bugbountylibrary.com/r/c4%3A2023-05-ajna%3AM-09.md).
- Slate accepts duplicate proposal ids, inflating budget and locking out legitimate proposals [E3](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-6.md).
- Attacker pre-registers a reserved `_confirm` name, blocking confirmation ballots [E4](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-13.md).

## Impact and severity
In the evidence this produced governance denial of service: legitimate proposals could not be created, funded, or executed, sometimes for days [E1](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-12.md)[E2](https://bugbountylibrary.com/r/c4%3A2023-05-ajna%3AM-09.md)[E3](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-6.md)[E4](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-13.md). Severity rises when the blocking is cheap, persistent, or affects safety-critical updates such as price feeds [E4](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-13.md).

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-12.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-05-ajna%3AM-09.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-6.md
- E4: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-13.md
